- Data Set Description For this project, our team decided to take a data-driven approach to evaluate music. We are using the Spotify Dataset from 1921 to 2020 that consists of over 160,000 different tracks. In order to stay consistent in the evaluation, all data was sourced from the Spotify Web API. The following set of data is combination of Primary, Numerical, Dummy(binary), and Categorical data. This allows us to explore different types of models and draw different insights. Here are all the variables that is included in the data set:
Primary - id: this an unique key comprised of numbers and characters that is assigned to each track generated by Spotify
Numerical - acousticness: range from 0(LOW) to 1(HIGH) - danceability: range from 0(LOW) to 1(HIGH) - energy: range from 0(LOW) to 1(HIGH) - duration_ms: majority range from 200,000 to 300,000 - instrumentalness: range from 0(LOW) to 1(HIGH) - valence: range from 0(LOW) to 1(HIGH) - popularity: range from 0(LOW) to 100(HIGH)* - tempo: majority range from 50(LOW) to 150(high) - liveness: range from 0(LOW) to 1(HIGH) - loudness majority range from -60 to 0 - speechiness: range from 0(LOW) to 1(HIGH) - year: range from 1921 to 2020
Dummy - mode: 0 represents minor and 1 represents major - explicit: 0 represents no explicit content and 1 represents explicit content
Categorical - key: this consists of all different music keys on onctave encoded from 0 to 11 (i.e. C = 0, C# = 1, etc…) - artists: the artist of the track - release_date: the date of release in yyyy-mm-dd format - name: the name of the track
*With our approach on evaluating different tracks, we decided to use popularity as our main dependant variable.
options(repr.matrix.max.rows=100, repr.matrix.max.cols=20)
Import all Libraries
# Libraries
options(warn=-1)
library(ggplot2)
library(dplyr)
library(plotly)
library(hrbrthemes)
library(forecast)
library(xts)
library(Metrics)
library(psych)
library(dygraphs)
library(GGally)
library(tidyverse)
library(tidyquant)
library(cranlogs)
library(corrr)
library(cowplot)
library(Metrics)
Annual data for each feature
df <- read.csv("./data/data.csv")
q1s = data.frame(matrix(ncol=3, nrow=0))
medians = data.frame(matrix(ncol=3, nrow=0))
q3s = data.frame(matrix(ncol=3, nrow=0))
feature_names = c("acousticness", "danceability", "instrumentalness", "energy",
"duration_ms", "valence", "tempo", "liveness", "loudness", "speechiness")
for(i in 1921:2020){
annual_val = subset(df, year==i)
for(name in feature_names){
vals = quantile(annual_val[,name])[2:4]
q1s <- rbind(q1s, c(i, name, vals[1]))
medians <- rbind(medians, c(i, name, vals[2]))
q3s <- rbind(q3s, c(i, name, vals[3]))
}
}
colnames(q1s) <-c("Year", "Feature", "Value")
colnames(medians) <-c("Year", "Feature", "Value")
colnames(q3s) <-c("Year", "Feature", "Value")
split_q1s <- split(q1s, q1s$Feature)
split_medians <- split(medians, medians$Feature)
split_q3s <- split(q3s, q3s$Feature)
feature_quantiles = array(0, c(100,3,length(feature_names)))
for(i in 1:length(feature_names)){
for(j in 1:100){
feature_quantiles[j,1,i] = split_q1s[[i]][[3]][j]
feature_quantiles[j,2,i] = split_medians[[i]][[3]][j]
feature_quantiles[j,3,i] = split_q3s[[i]][[3]][j]
}
}
dimnames(feature_quantiles) <- list(1921:2020,
c("Q1", "Median", "Q3"),
feature_names)
#acoustics = ts(feature_quantiles[,,'acousticness'], start=1921, end=2020, frequency=1)
#ggplot()+geom_line(data=acoustics[,"Median"], mapping=aes(x=1921:2020, y=as.numeric(acoustics[,"Median"]))) + geom_point() + geom_errorbar(aes(x = 1921:2020,ymin=as.numeric(acoustics[,"Q1"]), ymax=as.numeric(acoustics[,"Q3"])), )
#geom_line(mapping=aes(x=1921:2020, y = as.numeric(ts(feature_quantiles[,,'liveness'], start=1921, end=2020, frequency=1)[,"Median"])))
#acoustics
plot(1930,1, xlim = c(1921,2020), ylim = range(0,2), xlab="Year", ylab="Range")
for(i in 1:length(feature_names)){
name = feature_names[i]
medians = as.numeric(feature_quantiles[,,name][,"Median"])
# Normalize it????? / make it a function of its own mean
avg = mean(medians)
medians = medians / avg
lines(x=1921:2020, y=medians, col=i)
}
legend("topright", lty=1, col=c(1,2,3,4,5), legend=feature_names)

popular
q1s = data.frame(matrix(ncol=3, nrow=0))
medians = data.frame(matrix(ncol=3, nrow=0))
q3s = data.frame(matrix(ncol=3, nrow=0))
feature_names = sort(c("acousticness", "danceability", "instrumentalness", "energy",
"duration_ms", "valence", "tempo", "liveness", "loudness", "speechiness"))
for(i in 1921:2020){
annual_val = subset(df, year==i)
top_10 = floor(nrow(annual_val)*0.1)
annual_val = annual_val[order(annual_val$popularity, decreasing=TRUE),]
annual_val = annual_val[1:top_10,]
for(name in feature_names){
vals = quantile(annual_val[,name])[2:4]
q1s <- rbind(q1s, c(i, name, vals[1]))
medians <- rbind(medians, c(i, name, vals[2]))
q3s <- rbind(q3s, c(i, name, vals[3]))
}
}
colnames(q1s) <-c("Year", "Feature", "Value")
colnames(medians) <-c("Year", "Feature", "Value")
colnames(q3s) <-c("Year", "Feature", "Value")
split_q1s <- split(q1s, q1s$Feature)
split_medians <- split(medians, medians$Feature)
split_q3s <- split(q3s, q3s$Feature)
feature_quantiles = array(0, c(100,3,length(feature_names)))
for(i in 1:length(feature_names)){
for(j in 1:100){
feature_quantiles[j,1,i] = split_q1s[[i]][[3]][j]
feature_quantiles[j,2,i] = split_medians[[i]][[3]][j]
feature_quantiles[j,3,i] = split_q3s[[i]][[3]][j]
}
}
dimnames(feature_quantiles) <- list(1921:2020,
c("Q1", "Median", "Q3"),
feature_names)
#acoustics = ts(feature_quantiles[,,'acousticness'], start=1921, end=2020, frequency=1)
#ggplot()+geom_line(data=acoustics[,"Median"], mapping=aes(x=1921:2020, y=as.numeric(acoustics[,"Median"]))) + geom_point() + geom_errorbar(aes(x = 1921:2020,ymin=as.numeric(acoustics[,"Q1"]), ymax=as.numeric(acoustics[,"Q3"])), )
#geom_line(mapping=aes(x=1921:2020, y = as.numeric(ts(feature_quantiles[,,'liveness'], start=1921, end=2020, frequency=1)[,"Median"])))
#acoustics
plot(1930,1, xlim = c(1921,2020), ylim = range(0,2), xlab="Year", ylab="Range")
for(i in 1:length(feature_names)){
name = feature_names[i]
medians = as.numeric(feature_quantiles[,,name][,"Median"])
# Normalize it????? / make it a function of its own mean
avg = mean(medians)
medians = medians / avg
lines(x=1921:2020, y=medians, col=i)
}
legend("topright", lty=1, col=c(1,2,3,4,5), legend=feature_names)

Now trying to plot absolute values
# Get the list of features I need to work with
library(Metrics)
q1s = data.frame(matrix(ncol=3, nrow=0))
medians = data.frame(matrix(ncol=3, nrow=0))
best_medians = data.frame(matrix(ncol=3, nrow=0))
q3s = data.frame(matrix(ncol=3, nrow=0))
feature_names = sort(c("acousticness", "danceability", "instrumentalness", "energy",
"duration_ms", "valence", "tempo", "liveness", "loudness", "speechiness"))
for(i in 1921:2020){
annual_val = subset(df, year==i)
top_10 = floor(nrow(annual_val)*0.1)
annual_val = annual_val[order(annual_val$popularity, decreasing=TRUE),]
best_ones = annual_val[1:top_10,]
for(name in feature_names){
vals = quantile(annual_val[,name])[2:4]
best_vals = quantile(best_ones[,name])[2:4]
best_medians <- rbind(best_medians, c(i, name, best_vals[2]))
medians <- rbind(medians, c(i, name, vals[2]))
}
}
colnames(medians) <-c("Year", "Feature", "Value")
colnames(best_medians) <- c("Year", "Feature", "Value")
split_medians <- split(medians, medians$Feature)
split_best_medians <- split(best_medians, best_medians$Feature)
feature_quantiles = matrix(0, ncol=length(feature_names), nrow=100)
best_feature_quantiles = matrix(0, ncol=length(feature_names), nrow=100)
for(i in 1:length(feature_names)){
for(j in 1:100){
feature_quantiles[j,i] <- split_medians[[i]][[3]][j]
best_feature_quantiles[j,i] = split_best_medians[[i]][[3]][j]
}
}
dimnames(feature_quantiles) <- list(1921:2020,
feature_names)
dimnames(best_feature_quantiles) <- list(1921:2020,
feature_names)
for(i in 1:length(feature_names)){
name = feature_names[i]
scores = as.numeric(feature_quantiles[,i])
best_scores = as.numeric(best_feature_quantiles[,i])
mape = mape(best_scores, scores)
mape = round(mape * 100,3)
r = cor(scores, best_scores)
pval = t.test(scores, )
# Normalize it????? / make it a function of its own mean
smallest = min(min(scores), min(best_scores))
biggest = max(max(scores), max(best_scores))
plot(x=1921:2020, y=scores, col=1, type="l", ylim=c(smallest, biggest), main=name, sub=paste("MAPE: ", mape, "%;", "R:", r), xlab="Year", ylab="Median Value")
lines(x=1921:2020, y=best_scores, col=2)
legend("topright", lty=1, col=c(1,2), legend=c("Overall", "Most Popular"))
title()
}









NA

Train time series models to forecast the future models. Set up the libraries and the training/testing amounts
library("smooth")
library("forecast")
library("nnfor")
training.percent = 0.95
nTrain = 100*training.percent
nTest = 100*(1-training.percent)
Accousticness Models
acousticness = ts(as.numeric(feature_quantiles[,"acousticness"]) , start=1921)
acousticness.train = subset(acousticness, start=1, end=nTrain)
acousticness.test = subset(acousticness, start = (nTrain+1), end =(nTrain+nTest))
#ses
acousticness.train.ses <- ses(acousticness.train, h=nTest)
acousticness.ses.mape = mape(acousticness.train.ses$mean, acousticness.test)
plot(acousticness.train.ses, main=paste("Acousticness", "SES"), sub=paste("MAPE:", round(acousticness.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(acousticness)

#SARIMA
acousticness.sarima.model <- auto.arima(acousticness.train)
acousticness.sarima <- forecast(acousticness.sarima.model, h=nTest)
acousticness.sarima.mape = mape(acousticness.sarima$mean, acousticness.test)
plot(acousticness.sarima, main=paste("Acousticness", "SARIMA"), sub=paste("MAPE:", round(acousticness.sarima.mape*100,3), "%"), xlab="Year", ylab="Median Value")
lines(acousticness)

#Neural Network
acousticness.train.nn <- elm(acousticness.train)
acousticness.nn.forecast <- forecast(acousticness.train.nn, h=nTest)
acousticness.nn.mape = mape(acousticness.nn.forecast$mean, acousticness.test)
plot(acousticness, main=paste("Acousticness", "NN"), sub=paste("MAPE:", round(acousticness.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(acousticness.nn.forecast$mean, start=1921+nTrain), col=2)

Energy
energy = ts(as.numeric(feature_quantiles[,"energy"]) , start=1921)
energy.train = subset(energy, start=1, end=nTrain)
energy.test = subset(energy, start = (nTrain+1), end =(nTrain+nTest))
#SES
energy.train.ses <- ses(energy.train, h=nTest)
energy.ses.mape = mape(energy.train.ses$mean, energy.test)
plot(energy.train.ses, main=paste("Energy", "SES"), sub=paste("MAPE:", round(energy.ses.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(energy)

#SARIMA
energy.sarima.model <- auto.arima(energy.train)
energy.sarima <- forecast(energy.sarima.model, h=nTest)
energy.sarima.mape = mape(energy.sarima$mean, energy.test)
plot(energy.sarima, main=paste("Energy", "SARIMA"), sub=paste("MAPE:", round(energy.sarima.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(energy)

#Neural Network
energy.train.nn <- elm(energy.train)
energy.nn.forecast <- forecast(energy.train.nn, h=nTest)
energy.nn.mape = mape(energy.nn.forecast$mean, energy.test)
plot(energy, main=paste("Energy", "NN"), sub=paste("MAPE:", round(energy.nn.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(energy.nn.forecast$mean, start=1921+nTrain), col=2)

Danceability
library("smooth")
library("forecast")
library(nnfor)
training.percent = 0.95
nTrain = 100*training.percent
nTest = 100*(1-training.percent)
#Acousticness
acousticness = ts(as.numeric(feature_quantiles[,"acousticness"]) , start=1921)
acousticness.train = subset(acousticness, start=1, end=nTrain)
acousticness.test = subset(acousticness, start = (nTrain+1), end =(nTrain+nTest))
#ses
acousticness.train.ses <- ses(acousticness.train, h=nTest)
acousticness.ses.mape = mape(acousticness.train.ses$mean, acousticness.test)
plot(acousticness.train.ses, main=paste("Acousticness", "SES"), sub=paste("MAPE:", round(acousticness.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(acousticness)

#SARIMA
acousticness.sarima.model <- auto.arima(acousticness.train)
acousticness.sarima <- forecast(acousticness.sarima.model, h=nTest)
acousticness.sarima.mape = mape(acousticness.sarima$mean, acousticness.test)
plot(acousticness.sarima, main=paste("Acousticness", "SARIMA"), sub=paste("MAPE:", round(acousticness.sarima.mape*100,3), "%"), xlab="Year", ylab="Median Value")
lines(acousticness)

#Neural Network
acousticness.train.nn <- elm(acousticness.train)
acousticness.nn.forecast <- forecast(acousticness.train.nn, h=nTest)
acousticness.nn.mape = mape(acousticness.nn.forecast$mean, acousticness.test)
plot(acousticness, main=paste("Acousticness", "NN"), sub=paste("MAPE:", round(acousticness.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(acousticness.nn.forecast$mean, start=1921+nTrain), col=2)

#Energy
energy = ts(as.numeric(feature_quantiles[,"energy"]) , start=1921)
energy.train = subset(energy, start=1, end=nTrain)
energy.test = subset(energy, start = (nTrain+1), end =(nTrain+nTest))
#SES
energy.train.ses <- ses(energy.train, h=nTest)
energy.ses.mape = mape(energy.train.ses$mean, energy.test)
plot(energy.train.ses, main=paste("Energy", "SES"), sub=paste("MAPE:", round(energy.ses.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(energy)

#SARIMA
energy.sarima.model <- auto.arima(energy.train)
energy.sarima <- forecast(energy.sarima.model, h=nTest)
energy.sarima.mape = mape(energy.sarima$mean, energy.test)
plot(energy.sarima, main=paste("Energy", "SARIMA"), sub=paste("MAPE:", round(energy.sarima.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(energy)

#Neural Network
energy.train.nn <- elm(energy.train)
energy.nn.forecast <- forecast(energy.train.nn, h=nTest)
energy.nn.mape = mape(energy.nn.forecast$mean, energy.test)
plot(energy, main=paste("Energy", "NN"), sub=paste("MAPE:", round(energy.nn.mape* 100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(energy.nn.forecast$mean, start=1921+nTrain), col=2)

#Duration
duration = ts(as.numeric(feature_quantiles[,"duration_ms"]) , start=1921)
duration.train = subset(duration, start=1, end=nTrain)
duration.test = subset(duration, start = (nTrain+1), end =(nTrain+nTest))
#SES
duration.train.ses <- ses(duration.train, h=nTest)
duration.ses.mape = mape(duration.train.ses$mean, duration.test)
plot(duration.train.ses, main=paste("Duration", "SES"), sub=paste("MAPE:",round(duration.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(duration)

#SARIMA
duration.sarima.model <- auto.arima(duration.train)
duration.sarima <- forecast(duration.sarima.model, h=nTest)
duration.sarima.mape = mape(duration.sarima$mean, duration.test)
plot(duration.sarima, main=paste("Duration", "SARIMA"), sub=paste("MAPE:", round(duration.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(duration)

#Neural Network
duration.train.nn <- elm(duration.train)
duration.nn.forecast <- forecast(duration.train.nn, h=nTest)
duration.nn.mape = mape(duration.nn.forecast$mean, duration.test)
plot(duration, main=paste("Duration", "NN"), sub=paste("MAPE:", round(duration.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(duration.nn.forecast$mean, start=1921+nTrain), col=2)

Duration
duration = ts(as.numeric(feature_quantiles[,"duration_ms"]) , start=1921)
duration.train = subset(duration, start=1, end=nTrain)
duration.test = subset(duration, start = (nTrain+1), end =(nTrain+nTest))
#SES
duration.train.ses <- ses(duration.train, h=nTest)
duration.ses.mape = mape(duration.train.ses$mean, duration.test)
plot(duration.train.ses, main=paste("Duration", "SES"), sub=paste("MAPE:",round(duration.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(duration)

#SARIMA
duration.sarima.model <- auto.arima(duration.train)
duration.sarima <- forecast(duration.sarima.model, h=nTest)
duration.sarima.mape = mape(duration.sarima$mean, duration.test)
plot(duration.sarima, main=paste("Duration", "SARIMA"), sub=paste("MAPE:", round(duration.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(duration)

#Neural Network
duration.train.nn <- elm(duration.train)
duration.nn.forecast <- forecast(duration.train.nn, h=nTest)
duration.nn.mape = mape(duration.nn.forecast$mean, duration.test)
plot(duration, main=paste("Duration", "NN"), sub=paste("MAPE:", round(duration.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(duration.nn.forecast$mean, start=1921+nTrain), col=2)

Valence
valence = ts(as.numeric(feature_quantiles[,"valence"]) , start=1921)
valence.train = subset(valence, start=1, end=nTrain)
valence.test = subset(valence, start = (nTrain+1), end =(nTrain+nTest))
#SES
valence.train.ses <- ses(valence.train, h=nTest)
valence.ses.mape = mape(valence.train.ses$mean, valence.test)
plot(valence.train.ses, main=paste("valence", "SES"), sub=paste("MAPE:",round(valence.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(valence)

#SARIMA
valence.sarima.model <- auto.arima(valence.train)
valence.sarima <- forecast(valence.sarima.model, h=nTest)
valence.sarima.mape = mape(valence.sarima$mean, valence.test)
plot(valence.sarima, main=paste("valence", "SARIMA"), sub=paste("MAPE:", round(valence.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(valence)

#Neural Network
valence.train.nn <- elm(valence.train)
valence.nn.forecast <- forecast(valence.train.nn, h=nTest)
valence.nn.mape = mape(valence.nn.forecast$mean, valence.test)
plot(valence, main=paste("valence", "NN"), sub=paste("MAPE:", round(valence.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(valence.nn.forecast$mean, start=1921+nTrain), col=2)

Tempo
tempo = ts(as.numeric(feature_quantiles[,"tempo"]) , start=1921)
tempo.train = subset(tempo, start=1, end=nTrain)
tempo.test = subset(tempo, start = (nTrain+1), end =(nTrain+nTest))
#SES
tempo.train.ses <- ses(tempo.train, h=nTest)
tempo.ses.mape = mape(tempo.train.ses$mean, tempo.test)
plot(tempo.train.ses, main=paste("tempo", "SES"), sub=paste("MAPE:",round(tempo.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(tempo)

#SARIMA
tempo.sarima.model <- auto.arima(tempo.train)
tempo.sarima <- forecast(tempo.sarima.model, h=nTest)
tempo.sarima.mape = mape(tempo.sarima$mean, tempo.test)
plot(tempo.sarima, main=paste("tempo", "SARIMA"), sub=paste("MAPE:", round(tempo.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(tempo)

#Neural Network
tempo.train.nn <- elm(tempo.train)
tempo.nn.forecast <- forecast(tempo.train.nn, h=nTest)
tempo.nn.mape = mape(tempo.nn.forecast$mean, tempo.test)
plot(tempo, main=paste("tempo", "NN"), sub=paste("MAPE:", round(tempo.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(tempo.nn.forecast$mean, start=1921+nTrain), col=2)

Liveness
liveness = ts(as.numeric(feature_quantiles[,"liveness"]) , start=1921)
liveness.train = subset(liveness, start=1, end=nTrain)
liveness.test = subset(liveness, start = (nTrain+1), end =(nTrain+nTest))
#SES
liveness.train.ses <- ses(liveness.train, h=nTest)
liveness.ses.mape = mape(liveness.train.ses$mean, liveness.test)
plot(liveness.train.ses, main=paste("liveness", "SES"), sub=paste("MAPE:",round(liveness.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(liveness)

#SARIMA
liveness.sarima.model <- auto.arima(liveness.train)
liveness.sarima <- forecast(liveness.sarima.model, h=nTest)
liveness.sarima.mape = mape(liveness.sarima$mean, liveness.test)
plot(liveness.sarima, main=paste("liveness", "SARIMA"), sub=paste("MAPE:", round(liveness.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(liveness)

#Neural Network
liveness.train.nn <- elm(liveness.train)
liveness.nn.forecast <- forecast(liveness.train.nn, h=nTest)
liveness.nn.mape = mape(liveness.nn.forecast$mean, liveness.test)
plot(liveness, main=paste("liveness", "NN"), sub=paste("MAPE:", round(liveness.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(liveness.nn.forecast$mean, start=1921+nTrain), col=2)

Loudness
loudness = ts(as.numeric(feature_quantiles[,"loudness"]) , start=1921)
loudness.train = subset(loudness, start=1, end=nTrain)
loudness.test = subset(loudness, start = (nTrain+1), end =(nTrain+nTest))
#SES
loudness.train.ses <- ses(loudness.train, h=nTest)
loudness.ses.mape = mape(loudness.train.ses$mean, loudness.test)
plot(loudness.train.ses, main=paste("loudness", "SES"), sub=paste("MAPE:",round(loudness.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(loudness)

#SARIMA
loudness.sarima.model <- auto.arima(loudness.train)
loudness.sarima <- forecast(loudness.sarima.model, h=nTest)
loudness.sarima.mape = mape(loudness.sarima$mean, loudness.test)
plot(loudness.sarima, main=paste("loudness", "SARIMA"), sub=paste("MAPE:", round(loudness.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(loudness)

#Neural Network
loudness.train.nn <- elm(loudness.train)
loudness.nn.forecast <- forecast(loudness.train.nn, h=nTest)
loudness.nn.mape = mape(loudness.nn.forecast$mean, loudness.test)
plot(loudness, main=paste("loudness", "NN"), sub=paste("MAPE:", round(loudness.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(loudness.nn.forecast$mean, start=1921+nTrain), col=2)

Speechiness
speechiness = ts(as.numeric(feature_quantiles[,"speechiness"]) , start=1921)
speechiness.train = subset(speechiness, start=1, end=nTrain)
speechiness.test = subset(speechiness, start = (nTrain+1), end =(nTrain+nTest))
#SES
speechiness.train.ses <- ses(speechiness.train, h=nTest)
speechiness.ses.mape = mape(speechiness.train.ses$mean, speechiness.test)
plot(speechiness.train.ses, main=paste("speechiness", "SES"), sub=paste("MAPE:",round(speechiness.ses.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(speechiness)

#SARIMA
speechiness.sarima.model <- auto.arima(speechiness.train)
speechiness.sarima <- forecast(speechiness.sarima.model, h=nTest)
speechiness.sarima.mape = mape(speechiness.sarima$mean, speechiness.test)
plot(speechiness.sarima, main=paste("speechiness", "SARIMA"), sub=paste("MAPE:", round(speechiness.sarima.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(speechiness)

#Neural Network
speechiness.train.nn <- elm(speechiness.train)
speechiness.nn.forecast <- forecast(speechiness.train.nn, h=nTest)
speechiness.nn.mape = mape(speechiness.nn.forecast$mean, speechiness.test)
plot(speechiness, main=paste("speechiness", "NN"), sub=paste("MAPE:", round(speechiness.nn.mape*100, 3), "%"), xlab="Year", ylab="Median Value")
lines(ts(speechiness.nn.forecast$mean, start=1921+nTrain), col=2)

Creating linear models for predicting popular features based on their current feature
#Energy
best_energy = ts(as.numeric(best_feature_quantiles[,"energy"]), start=1921, end=2020)
best_energy.train = subset(best_energy, start = 1, end = nTrain)
best_energy.test = subset(best_energy, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_energy.train, overall = energy.train)
best = best_energy.train
overall = energy.train
energy.lm <- lm(best ~ overall)
test_df <- data.frame(overall = energy.test)
best_energy.test.predict <- predict(energy.lm, newdata=test_df, interval="prediction")
best_energy_test_predict.mape <- mape(best_energy.test[1:length(best_energy.test)], best_energy.test.predict[,1])
plot(best_energy,xlab="Year", ylab="Energy", main=paste("Energy Population Prediction"), sub = paste("MAPE: ", round(best_energy_test_predict.mape*100, 3), "%"))
lines(ts(best_energy.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Duration
best_duration = ts(as.numeric(best_feature_quantiles[,"duration_ms"]), start=1921, end=2020)
best_duration.train = subset(best_duration, start = 1, end = nTrain)
best_duration.test = subset(best_duration, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_duration.train, overall = duration.train)
best = best_duration.train
overall = duration.train
duration.lm <- lm(best ~ overall)
test_df <- data.frame(overall = duration.test)
best_duration.test.predict <- predict(duration.lm, newdata=test_df, interval="prediction")
best_duration_test.predict.mape <- mape(best_duration.test[1:length(best_duration.test)], best_duration.test.predict[,1])
plot(best_duration,xlab="Year", ylab="duration", main=paste("duration Popularity Prediction"), sub = paste("MAPE: ", round(best_duration_test.predict.mape*100, 3), "%"))
lines(ts(best_duration.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Accousticness
best_acousticness = ts(as.numeric(best_feature_quantiles[,"acousticness"]), start=1921, end=2020)
best_acousticness.train = subset(best_acousticness, start = 1, end = nTrain)
best_acousticness.test = subset(best_acousticness, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_acousticness.train, overall = acousticness.train)
best = best_acousticness.train
overall = acousticness.train
acousticness.lm <- lm(best ~ overall)
test_df <- data.frame(overall = acousticness.test)
best_acousticness.test.predict <- predict(acousticness.lm, newdata=test_df, interval="prediction")
best_acousticness_test.predict.mape <- mape(best_acousticness.test[1:length(best_acousticness.test)], best_acousticness.test.predict[,1])
plot(best_acousticness,xlab="Year", ylab="acousticness", main=paste("acousticness Popularity Prediction"), sub = paste("MAPE: ", round(best_acousticness_test.predict.mape*100, 3), "%"))
lines(ts(best_acousticness.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Valence
best_valence = ts(as.numeric(best_feature_quantiles[,"valence"]), start=1921, end=2020)
best_valence.train = subset(best_valence, start = 1, end = nTrain)
best_valence.test = subset(best_valence, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_valence.train, overall = valence.train)
best = best_valence.train
overall = valence.train
valence.lm <- lm(best ~ overall)
test_df <- data.frame(overall = valence.test)
best_valence.test.predict <- predict(valence.lm, newdata=test_df, interval="prediction")
best_valence_test.predict.mape <- mape(best_valence.test[1:length(best_valence.test)], best_valence.test.predict[,1])
plot(best_valence,xlab="Year", ylab="Valence", main=paste("Valence Popularity Prediction"), sub = paste("MAPE: ", round(best_valence_test.predict.mape*100, 3), "%"))
lines(ts(best_valence.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Tempo
best_tempo = ts(as.numeric(best_feature_quantiles[,"tempo"]), start=1921, end=2020)
best_tempo.train = subset(best_tempo, start = 1, end = nTrain)
best_tempo.test = subset(best_tempo, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_tempo.train, overall = tempo.train)
best = best_tempo.train
overall = tempo.train
tempo.lm <- lm(best ~ overall)
test_df <- data.frame(overall = tempo.test)
best_tempo.test.predict <- predict(tempo.lm, newdata=test_df, interval="prediction")
best_tempo_test.predict.mape <- mape(best_tempo.test[1:length(best_tempo.test)], best_tempo.test.predict[,1])
plot(best_tempo,xlab="Year", ylab="tempo", main=paste("tempo Popularity Prediction"), sub = paste("MAPE: ", round(best_tempo_test.predict.mape*100, 3), "%"))
lines(ts(best_tempo.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Liveness
best_liveness = ts(as.numeric(best_feature_quantiles[,"liveness"]), start=1921, end=2020)
best_liveness.train = subset(best_liveness, start = 1, end = nTrain)
best_liveness.test = subset(best_liveness, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_liveness.train, overall = liveness.train)
best = best_liveness.train
overall = liveness.train
liveness.lm <- lm(best ~ overall)
test_df <- data.frame(overall = liveness.test)
best_liveness.test.predict <- predict(liveness.lm, newdata=test_df, interval="prediction")
best_liveness_test.predict.mape <- mape(best_liveness.test[1:length(best_liveness.test)], best_liveness.test.predict[,1])
plot(best_liveness,xlab="Year", ylab="liveness", main=paste("liveness Popularity Prediction"), sub = paste("MAPE: ", round(best_liveness_test.predict.mape*100, 3), "%"))
lines(ts(best_liveness.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Loudness
best_loudness = ts(as.numeric(best_feature_quantiles[,"loudness"]), start=1921, end=2020)
best_loudness.train = subset(best_loudness, start = 1, end = nTrain)
best_loudness.test = subset(best_loudness, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_loudness.train, overall = loudness.train)
best = best_loudness.train
overall = loudness.train
loudness.lm <- lm(best ~ overall)
test_df <- data.frame(overall = loudness.test)
best_loudness.test.predict <- predict(loudness.lm, newdata=test_df, interval="prediction")
best_loudness_test.predict.mape <- mape(best_loudness.test[1:length(best_loudness.test)], best_loudness.test.predict[,1])
plot(best_loudness,xlab="Year", ylab="loudness", main=paste("loudness Popularity Prediction"), sub = paste("MAPE: ", round(best_loudness_test.predict.mape*100, 3), "%"))
lines(ts(best_loudness.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

#Speechiness
best_speechiness = ts(as.numeric(best_feature_quantiles[,"speechiness"]), start=1921, end=2020)
best_speechiness.train = subset(best_speechiness, start = 1, end = nTrain)
best_speechiness.test = subset(best_speechiness, start = nTrain+1, end = nTrain+nTest)
train_df = data.frame(best = best_speechiness.train, overall = speechiness.train)
best = best_speechiness.train
overall = speechiness.train
speechiness.lm <- lm(best ~ overall)
test_df <- data.frame(overall = speechiness.test)
best_speechiness.test.predict <- predict(speechiness.lm, newdata=test_df, interval="prediction")
best_speechiness_test.predict.mape <- mape(best_speechiness.test.predict[,1], best_speechiness.test[1:length(best_speechiness.test)])
plot(best_speechiness,xlab="Year", ylab="speechiness", main=paste("speechiness Popularity Prediction"), sub = paste("MAPE: ", round(best_speechiness_test.predict.mape*100, 3), "%"))
lines(ts(best_speechiness.test.predict[,1], start = 1921+nTrain+1, end = 2020), col = 2)

Predict features for the overall market trends for the next five years. We will only use models that had the low enough MAPEs (10% chosen as a threshold) based on potential timeseries models.
Energy: NN Model
energy.nn <- elm(energy)
energy.nn.predict <- forecast(energy.nn, h=nTest)
plot(energy.nn.predict, main=paste("Energy", "NN"), xlab="Year", ylab="Median Value")
lines(energy)

Duration: NN Model
duration.nn <- elm(duration)
duration.nn.predict <- forecast(duration.nn, h=nTest)
plot(duration.nn.predict, main=paste("Duration", "NN"), xlab="Year", ylab="Median Value")

Tempo: SARIMA Model
tempo.sarima <- auto.arima(tempo)
tempo.sarima.predict <- forecast(tempo.sarima, h=nTest)
plot(tempo.sarima.predict, main=paste("Tempo", "SARIMA"), xlab="Year", ylab="Median Value")
lines(tempo)

Valence: SARIMA Model
valence.sarima <- auto.arima(valence)
valence.sarima.predict <- forecast(valence.sarima, h=nTest)
plot(valence.sarima.predict, main=paste("Valence", "SARIMA"), xlab="Year", ylab="Median Value")
lines(valence)

Liveness: SARIMA Model
liveness.sarima <- auto.arima(liveness)
liveness.sarima.predict <- forecast(liveness.sarima, h=nTest)
plot(liveness.sarima.predict, main=paste("liveness", "SARIMA"), xlab="Year", ylab="Median Value")
lines(liveness)

Loudness: SES Model
loudness.ses <- ses(loudness, h=nTest)
plot(loudness.ses, main=paste("loudness", "SES"), xlab="Year", ylab="Median Value")
lines(loudness)

Now predicting the median values for the most popular songs for this set of features.
Energy
best = best_energy
overall = energy
energy.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = energy.nn.predict$mean)
energy.predict <- predict(energy.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(energy, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's Energy Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(energy.nn.predict$mean, 3), collapse=",") ))
lines(best_energy, col = 2)
lines(ts(energy.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("bottomright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

Duration
best = best_duration
overall = duration
duration.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = duration.nn.predict$mean)
duration.predict <- predict(duration.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(duration, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's Duration Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(duration.nn.predict$mean), collapse=",") ))
lines(best_duration, col = 2)
lines(ts(duration.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("topright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

Tempo
best = best_tempo
overall = tempo
tempo.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = tempo.sarima.predict$mean)
tempo.predict <- predict(tempo.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(tempo, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's Tempo Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(tempo.sarima.predict$mean), collapse=",") ))
lines(best_tempo, col = 2)
lines(ts(tempo.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("bottomright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

Valence
best = best_valence
overall = valence
valence.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = valence.sarima.predict$mean)
valence.predict <- predict(valence.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(valence, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's valence Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(valence.sarima.predict$mean,3), collapse=",") ))
lines(best_valence, col = 2)
lines(ts(valence.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("bottomright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

Liveness
best = best_liveness
overall = liveness
liveness.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = liveness.sarima.predict$mean)
liveness.predict <- predict(liveness.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(liveness, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's liveness Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(liveness.sarima.predict$mean,3), collapse=",") ))
lines(best_liveness, col = 2)
lines(ts(liveness.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("topright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

Loudness
best = best_loudness
overall = loudness
loudness.lm <- lm(best ~ overall)
predict_df <- data.frame(overall = loudness.ses$mean)
loudness.predict <- predict(loudness.lm, newdata=predict_df, interval="prediction")
smallest = min(min(best), min(overall))
biggest = max(max(best), max(overall))
plot(loudness, xlab="Year", ylab="Median Value", ylim=c(smallest, biggest), main="Most Popular Song's loudness Feature Prediction", sub=paste("Projected median feature value for most popular songs:", paste(round(loudness.ses$mean,1), collapse=",") ))
lines(best_loudness, col = 2)
lines(ts(loudness.predict[,1], start=2020+1, end=2020+nTest), col = 3)
legend("bottomright", lty=1, col=c(1,2,3), legend=c("Overall", "Most Popular", "Most Popular Projection"))

LS0tCnRpdGxlOiAiU3BvdGlmeSBEYXRhIEFuYWx5c2lzIGFuZCBEZXNjcmlwdGl2ZSBTdGF0aXN0aWNzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBDUyBEdWFscwpkYXRlOiBOb3ZlbWJlciAxOCwgMjAyMAotLS0KCjEuIERhdGEgU2V0IERlc2NyaXB0aW9uCkZvciB0aGlzIHByb2plY3QsIG91ciB0ZWFtIGRlY2lkZWQgdG8gdGFrZSBhIGRhdGEtZHJpdmVuIGFwcHJvYWNoIHRvIGV2YWx1YXRlIG11c2ljLiBXZSBhcmUgdXNpbmcgdGhlIFNwb3RpZnkgRGF0YXNldCBmcm9tIDE5MjEgdG8gMjAyMCB0aGF0IGNvbnNpc3RzIG9mIG92ZXIgMTYwLDAwMCBkaWZmZXJlbnQgdHJhY2tzLiBJbiBvcmRlciB0byBzdGF5IGNvbnNpc3RlbnQgaW4gdGhlIGV2YWx1YXRpb24sIGFsbCBkYXRhIHdhcyBzb3VyY2VkIGZyb20gdGhlIFNwb3RpZnkgV2ViIEFQSS4gVGhlIGZvbGxvd2luZyBzZXQgb2YgZGF0YSBpcyBjb21iaW5hdGlvbiBvZiBQcmltYXJ5LCBOdW1lcmljYWwsIER1bW15KGJpbmFyeSksIGFuZCBDYXRlZ29yaWNhbCBkYXRhLiBUaGlzIGFsbG93cyB1cyB0byBleHBsb3JlIGRpZmZlcmVudCB0eXBlcyBvZiBtb2RlbHMgYW5kIGRyYXcgZGlmZmVyZW50IGluc2lnaHRzLiBIZXJlIGFyZSBhbGwgdGhlIHZhcmlhYmxlcyB0aGF0IGlzIGluY2x1ZGVkIGluIHRoZSBkYXRhIHNldDoKClByaW1hcnkKICAgIC0gaWQ6IHRoaXMgYW4gdW5pcXVlIGtleSBjb21wcmlzZWQgb2YgbnVtYmVycyBhbmQgY2hhcmFjdGVycyB0aGF0IGlzIGFzc2lnbmVkIHRvIGVhY2ggdHJhY2sgZ2VuZXJhdGVkIGJ5IFNwb3RpZnkKCk51bWVyaWNhbAogICAgLSBhY291c3RpY25lc3M6IHJhbmdlIGZyb20gMChMT1cpIHRvIDEoSElHSCkKICAgIC0gZGFuY2VhYmlsaXR5OiByYW5nZSBmcm9tIDAoTE9XKSB0byAxKEhJR0gpCiAgICAtIGVuZXJneTogcmFuZ2UgZnJvbSAwKExPVykgdG8gMShISUdIKQogICAgLSBkdXJhdGlvbl9tczogbWFqb3JpdHkgcmFuZ2UgZnJvbSAyMDAsMDAwIHRvIDMwMCwwMDAKICAgIC0gaW5zdHJ1bWVudGFsbmVzczogcmFuZ2UgZnJvbSAwKExPVykgdG8gMShISUdIKQogICAgLSB2YWxlbmNlOiByYW5nZSBmcm9tIDAoTE9XKSB0byAxKEhJR0gpCiAgICAtIHBvcHVsYXJpdHk6IHJhbmdlIGZyb20gMChMT1cpIHRvIDEwMChISUdIKSoKICAgIC0gdGVtcG86IG1ham9yaXR5IHJhbmdlIGZyb20gNTAoTE9XKSB0byAxNTAoaGlnaCkKICAgIC0gbGl2ZW5lc3M6IHJhbmdlIGZyb20gMChMT1cpIHRvIDEoSElHSCkKICAgIC0gbG91ZG5lc3MgbWFqb3JpdHkgcmFuZ2UgZnJvbSAtNjAgdG8gMAogICAgLSBzcGVlY2hpbmVzczogcmFuZ2UgZnJvbSAwKExPVykgdG8gMShISUdIKQogICAgLSB5ZWFyOiByYW5nZSBmcm9tIDE5MjEgdG8gMjAyMAoKRHVtbXkKICAgIC0gbW9kZTogMCByZXByZXNlbnRzIG1pbm9yIGFuZCAxIHJlcHJlc2VudHMgbWFqb3IKICAgIC0gZXhwbGljaXQ6IDAgcmVwcmVzZW50cyBubyBleHBsaWNpdCBjb250ZW50IGFuZCAxIHJlcHJlc2VudHMgZXhwbGljaXQgY29udGVudAogICAgCkNhdGVnb3JpY2FsCiAgICAtIGtleTogdGhpcyBjb25zaXN0cyBvZiBhbGwgZGlmZmVyZW50IG11c2ljIGtleXMgb24gb25jdGF2ZSBlbmNvZGVkIGZyb20gMCB0byAxMSAoaS5lLiBDID0gMCwgQyMgPSAxLCBldGMuLi4pCiAgICAtIGFydGlzdHM6IHRoZSBhcnRpc3Qgb2YgdGhlIHRyYWNrCiAgICAtIHJlbGVhc2VfZGF0ZTogdGhlIGRhdGUgb2YgcmVsZWFzZSBpbiB5eXl5LW1tLWRkIGZvcm1hdAogICAgLSBuYW1lOiB0aGUgbmFtZSBvZiB0aGUgdHJhY2sKCipXaXRoIG91ciBhcHByb2FjaCBvbiBldmFsdWF0aW5nIGRpZmZlcmVudCB0cmFja3MsIHdlIGRlY2lkZWQgdG8gdXNlIHBvcHVsYXJpdHkgYXMgb3VyIG1haW4gZGVwZW5kYW50IHZhcmlhYmxlLiAKCgpgYGB7cn0Kb3B0aW9ucyhyZXByLm1hdHJpeC5tYXgucm93cz0xMDAsIHJlcHIubWF0cml4Lm1heC5jb2xzPTIwKQpgYGAKCgpJbXBvcnQgYWxsIExpYnJhcmllcwpgYGB7cn0KIyBMaWJyYXJpZXMKb3B0aW9ucyh3YXJuPS0xKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGhyYnJ0aGVtZXMpCmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkoeHRzKQpsaWJyYXJ5KE1ldHJpY3MpCmxpYnJhcnkocHN5Y2gpCmxpYnJhcnkoZHlncmFwaHMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5cXVhbnQpICAKbGlicmFyeShjcmFubG9ncykgICAKbGlicmFyeShjb3JycikgICAgICAKbGlicmFyeShjb3dwbG90KSAgCmxpYnJhcnkoTWV0cmljcykKYGBgCgpBbm51YWwgZGF0YSBmb3IgZWFjaCBmZWF0dXJlCmBgYHtyfQpkZiA8LSByZWFkLmNzdigiLi9kYXRhL2RhdGEuY3N2IikKcTFzID0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD0zLCBucm93PTApKQptZWRpYW5zID0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD0zLCBucm93PTApKQpxM3MgPSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTMsIG5yb3c9MCkpCmZlYXR1cmVfbmFtZXMgPSBjKCJhY291c3RpY25lc3MiLCAiZGFuY2VhYmlsaXR5IiwgImluc3RydW1lbnRhbG5lc3MiLCAiZW5lcmd5IiwKICAgICAgICAgICAgICAgICAgImR1cmF0aW9uX21zIiwgInZhbGVuY2UiLCAidGVtcG8iLCAibGl2ZW5lc3MiLCAibG91ZG5lc3MiLCAic3BlZWNoaW5lc3MiKQpmb3IoaSBpbiAxOTIxOjIwMjApewogIGFubnVhbF92YWwgPSBzdWJzZXQoZGYsIHllYXI9PWkpCgogIGZvcihuYW1lIGluIGZlYXR1cmVfbmFtZXMpewogICAgdmFscyA9IHF1YW50aWxlKGFubnVhbF92YWxbLG5hbWVdKVsyOjRdCiAgICBxMXMgPC0gcmJpbmQocTFzLCBjKGksIG5hbWUsIHZhbHNbMV0pKQogICAgbWVkaWFucyA8LSByYmluZChtZWRpYW5zLCBjKGksIG5hbWUsIHZhbHNbMl0pKQogICAgcTNzIDwtIHJiaW5kKHEzcywgYyhpLCBuYW1lLCB2YWxzWzNdKSkKICB9Cn0KCmNvbG5hbWVzKHExcykgPC1jKCJZZWFyIiwgIkZlYXR1cmUiLCAiVmFsdWUiKQpjb2xuYW1lcyhtZWRpYW5zKSA8LWMoIlllYXIiLCAiRmVhdHVyZSIsICJWYWx1ZSIpCmNvbG5hbWVzKHEzcykgPC1jKCJZZWFyIiwgIkZlYXR1cmUiLCAiVmFsdWUiKQoKc3BsaXRfcTFzIDwtIHNwbGl0KHExcywgcTFzJEZlYXR1cmUpCnNwbGl0X21lZGlhbnMgPC0gc3BsaXQobWVkaWFucywgbWVkaWFucyRGZWF0dXJlKQpzcGxpdF9xM3MgPC0gc3BsaXQocTNzLCBxM3MkRmVhdHVyZSkKCmZlYXR1cmVfcXVhbnRpbGVzID0gYXJyYXkoMCwgYygxMDAsMyxsZW5ndGgoZmVhdHVyZV9uYW1lcykpKQoKZm9yKGkgaW4gMTpsZW5ndGgoZmVhdHVyZV9uYW1lcykpewogIGZvcihqIGluIDE6MTAwKXsKICAgIGZlYXR1cmVfcXVhbnRpbGVzW2osMSxpXSA9IHNwbGl0X3Exc1tbaV1dW1szXV1bal0KICAgIGZlYXR1cmVfcXVhbnRpbGVzW2osMixpXSA9IHNwbGl0X21lZGlhbnNbW2ldXVtbM11dW2pdCiAgICBmZWF0dXJlX3F1YW50aWxlc1tqLDMsaV0gPSBzcGxpdF9xM3NbW2ldXVtbM11dW2pdCiAgfQp9CgpkaW1uYW1lcyhmZWF0dXJlX3F1YW50aWxlcykgPC0gbGlzdCgxOTIxOjIwMjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIlExIiwgIk1lZGlhbiIsICJRMyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlX25hbWVzKQoKCiNhY291c3RpY3MgPSB0cyhmZWF0dXJlX3F1YW50aWxlc1ssLCdhY291c3RpY25lc3MnXSwgc3RhcnQ9MTkyMSwgZW5kPTIwMjAsIGZyZXF1ZW5jeT0xKQojZ2dwbG90KCkrZ2VvbV9saW5lKGRhdGE9YWNvdXN0aWNzWywiTWVkaWFuIl0sIG1hcHBpbmc9YWVzKHg9MTkyMToyMDIwLCB5PWFzLm51bWVyaWMoYWNvdXN0aWNzWywiTWVkaWFuIl0pKSkgKyAgZ2VvbV9wb2ludCgpICsgZ2VvbV9lcnJvcmJhcihhZXMoeCA9IDE5MjE6MjAyMCx5bWluPWFzLm51bWVyaWMoYWNvdXN0aWNzWywiUTEiXSksIHltYXg9YXMubnVtZXJpYyhhY291c3RpY3NbLCJRMyJdKSksICkKCiNnZW9tX2xpbmUobWFwcGluZz1hZXMoeD0xOTIxOjIwMjAsIHkgPSBhcy5udW1lcmljKHRzKGZlYXR1cmVfcXVhbnRpbGVzWywsJ2xpdmVuZXNzJ10sIHN0YXJ0PTE5MjEsIGVuZD0yMDIwLCBmcmVxdWVuY3k9MSlbLCJNZWRpYW4iXSkpKQoKI2Fjb3VzdGljcwoKcGxvdCgxOTMwLDEsIHhsaW0gPSBjKDE5MjEsMjAyMCksIHlsaW0gPSByYW5nZSgwLDIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iUmFuZ2UiKQpmb3IoaSBpbiAxOmxlbmd0aChmZWF0dXJlX25hbWVzKSl7CiAgbmFtZSA9IGZlYXR1cmVfbmFtZXNbaV0KICBtZWRpYW5zID0gYXMubnVtZXJpYyhmZWF0dXJlX3F1YW50aWxlc1ssLG5hbWVdWywiTWVkaWFuIl0pCiAgIyBOb3JtYWxpemUgaXQ/Pz8/PyAvIG1ha2UgaXQgYSBmdW5jdGlvbiBvZiBpdHMgb3duIG1lYW4KICAKICBhdmcgPSBtZWFuKG1lZGlhbnMpCiAgbWVkaWFucyA9IG1lZGlhbnMgLyBhdmcKICAKICBsaW5lcyh4PTE5MjE6MjAyMCwgeT1tZWRpYW5zLCBjb2w9aSkKfQpsZWdlbmQoInRvcHJpZ2h0IiwgbHR5PTEsIGNvbD1jKDEsMiwzLDQsNSksIGxlZ2VuZD1mZWF0dXJlX25hbWVzKQpgYGAKCgpwb3B1bGFyCmBgYHtyfQpxMXMgPSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTMsIG5yb3c9MCkpCm1lZGlhbnMgPSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTMsIG5yb3c9MCkpCnEzcyA9IGRhdGEuZnJhbWUobWF0cml4KG5jb2w9MywgbnJvdz0wKSkKZmVhdHVyZV9uYW1lcyA9IHNvcnQoYygiYWNvdXN0aWNuZXNzIiwgImRhbmNlYWJpbGl0eSIsICJpbnN0cnVtZW50YWxuZXNzIiwgImVuZXJneSIsCiAgICAgICAgICAgICAgICAgICJkdXJhdGlvbl9tcyIsICJ2YWxlbmNlIiwgInRlbXBvIiwgImxpdmVuZXNzIiwgImxvdWRuZXNzIiwgInNwZWVjaGluZXNzIikpCmZvcihpIGluIDE5MjE6MjAyMCl7CiAgYW5udWFsX3ZhbCA9IHN1YnNldChkZiwgeWVhcj09aSkKICAKICAKICB0b3BfMTAgPSBmbG9vcihucm93KGFubnVhbF92YWwpKjAuMSkKICBhbm51YWxfdmFsID0gYW5udWFsX3ZhbFtvcmRlcihhbm51YWxfdmFsJHBvcHVsYXJpdHksIGRlY3JlYXNpbmc9VFJVRSksXQogIGFubnVhbF92YWwgPSBhbm51YWxfdmFsWzE6dG9wXzEwLF0KCiAgZm9yKG5hbWUgaW4gZmVhdHVyZV9uYW1lcyl7CiAgICB2YWxzID0gcXVhbnRpbGUoYW5udWFsX3ZhbFssbmFtZV0pWzI6NF0KICAgIHExcyA8LSByYmluZChxMXMsIGMoaSwgbmFtZSwgdmFsc1sxXSkpCiAgICBtZWRpYW5zIDwtIHJiaW5kKG1lZGlhbnMsIGMoaSwgbmFtZSwgdmFsc1syXSkpCiAgICBxM3MgPC0gcmJpbmQocTNzLCBjKGksIG5hbWUsIHZhbHNbM10pKQogIH0KfQoKY29sbmFtZXMocTFzKSA8LWMoIlllYXIiLCAiRmVhdHVyZSIsICJWYWx1ZSIpCmNvbG5hbWVzKG1lZGlhbnMpIDwtYygiWWVhciIsICJGZWF0dXJlIiwgIlZhbHVlIikKY29sbmFtZXMocTNzKSA8LWMoIlllYXIiLCAiRmVhdHVyZSIsICJWYWx1ZSIpCgpzcGxpdF9xMXMgPC0gc3BsaXQocTFzLCBxMXMkRmVhdHVyZSkKc3BsaXRfbWVkaWFucyA8LSBzcGxpdChtZWRpYW5zLCBtZWRpYW5zJEZlYXR1cmUpCnNwbGl0X3EzcyA8LSBzcGxpdChxM3MsIHEzcyRGZWF0dXJlKQoKZmVhdHVyZV9xdWFudGlsZXMgPSBhcnJheSgwLCBjKDEwMCwzLGxlbmd0aChmZWF0dXJlX25hbWVzKSkpCgpmb3IoaSBpbiAxOmxlbmd0aChmZWF0dXJlX25hbWVzKSl7CiAgZm9yKGogaW4gMToxMDApewogICAgZmVhdHVyZV9xdWFudGlsZXNbaiwxLGldID0gc3BsaXRfcTFzW1tpXV1bWzNdXVtqXQogICAgZmVhdHVyZV9xdWFudGlsZXNbaiwyLGldID0gc3BsaXRfbWVkaWFuc1tbaV1dW1szXV1bal0KICAgIGZlYXR1cmVfcXVhbnRpbGVzW2osMyxpXSA9IHNwbGl0X3Ezc1tbaV1dW1szXV1bal0KICB9Cn0KCmRpbW5hbWVzKGZlYXR1cmVfcXVhbnRpbGVzKSA8LSBsaXN0KDE5MjE6MjAyMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiUTEiLCAiTWVkaWFuIiwgIlEzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfbmFtZXMpCgoKI2Fjb3VzdGljcyA9IHRzKGZlYXR1cmVfcXVhbnRpbGVzWywsJ2Fjb3VzdGljbmVzcyddLCBzdGFydD0xOTIxLCBlbmQ9MjAyMCwgZnJlcXVlbmN5PTEpCiNnZ3Bsb3QoKStnZW9tX2xpbmUoZGF0YT1hY291c3RpY3NbLCJNZWRpYW4iXSwgbWFwcGluZz1hZXMoeD0xOTIxOjIwMjAsIHk9YXMubnVtZXJpYyhhY291c3RpY3NbLCJNZWRpYW4iXSkpKSArICBnZW9tX3BvaW50KCkgKyBnZW9tX2Vycm9yYmFyKGFlcyh4ID0gMTkyMToyMDIwLHltaW49YXMubnVtZXJpYyhhY291c3RpY3NbLCJRMSJdKSwgeW1heD1hcy5udW1lcmljKGFjb3VzdGljc1ssIlEzIl0pKSwgKQoKI2dlb21fbGluZShtYXBwaW5nPWFlcyh4PTE5MjE6MjAyMCwgeSA9IGFzLm51bWVyaWModHMoZmVhdHVyZV9xdWFudGlsZXNbLCwnbGl2ZW5lc3MnXSwgc3RhcnQ9MTkyMSwgZW5kPTIwMjAsIGZyZXF1ZW5jeT0xKVssIk1lZGlhbiJdKSkpCgojYWNvdXN0aWNzCgpwbG90KDE5MzAsMSwgeGxpbSA9IGMoMTkyMSwyMDIwKSwgeWxpbSA9IHJhbmdlKDAsMiksIHhsYWI9IlllYXIiLCB5bGFiPSJSYW5nZSIpCmZvcihpIGluIDE6bGVuZ3RoKGZlYXR1cmVfbmFtZXMpKXsKICBuYW1lID0gZmVhdHVyZV9uYW1lc1tpXQogIG1lZGlhbnMgPSBhcy5udW1lcmljKGZlYXR1cmVfcXVhbnRpbGVzWywsbmFtZV1bLCJNZWRpYW4iXSkKICAjIE5vcm1hbGl6ZSBpdD8/Pz8/IC8gbWFrZSBpdCBhIGZ1bmN0aW9uIG9mIGl0cyBvd24gbWVhbgogIAogIGF2ZyA9IG1lYW4obWVkaWFucykKICBtZWRpYW5zID0gbWVkaWFucyAvIGF2ZwogIAogIGxpbmVzKHg9MTkyMToyMDIwLCB5PW1lZGlhbnMsIGNvbD1pKQp9CmxlZ2VuZCgidG9wcmlnaHQiLCBsdHk9MSwgY29sPWMoMSwyLDMsNCw1KSwgbGVnZW5kPWZlYXR1cmVfbmFtZXMpCmBgYAoKTm93IHRyeWluZyB0byBwbG90IGFic29sdXRlIHZhbHVlcwpgYGB7cn0KIyBHZXQgdGhlIGxpc3Qgb2YgZmVhdHVyZXMgSSBuZWVkIHRvIHdvcmsgd2l0aAoKbGlicmFyeShNZXRyaWNzKQoKcTFzID0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD0zLCBucm93PTApKQptZWRpYW5zID0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD0zLCBucm93PTApKQpiZXN0X21lZGlhbnMgPSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTMsIG5yb3c9MCkpCnEzcyA9IGRhdGEuZnJhbWUobWF0cml4KG5jb2w9MywgbnJvdz0wKSkKZmVhdHVyZV9uYW1lcyA9IHNvcnQoYygiYWNvdXN0aWNuZXNzIiwgImRhbmNlYWJpbGl0eSIsICJpbnN0cnVtZW50YWxuZXNzIiwgImVuZXJneSIsCiAgICAgICAgICAgICAgICAgICJkdXJhdGlvbl9tcyIsICJ2YWxlbmNlIiwgInRlbXBvIiwgImxpdmVuZXNzIiwgImxvdWRuZXNzIiwgInNwZWVjaGluZXNzIikpCmZvcihpIGluIDE5MjE6MjAyMCl7CiAgYW5udWFsX3ZhbCA9IHN1YnNldChkZiwgeWVhcj09aSkKICB0b3BfMTAgPSBmbG9vcihucm93KGFubnVhbF92YWwpKjAuMSkKICBhbm51YWxfdmFsID0gYW5udWFsX3ZhbFtvcmRlcihhbm51YWxfdmFsJHBvcHVsYXJpdHksIGRlY3JlYXNpbmc9VFJVRSksXQogIGJlc3Rfb25lcyA9IGFubnVhbF92YWxbMTp0b3BfMTAsXQoKICBmb3IobmFtZSBpbiBmZWF0dXJlX25hbWVzKXsKICAgIHZhbHMgPSBxdWFudGlsZShhbm51YWxfdmFsWyxuYW1lXSlbMjo0XQogICAgYmVzdF92YWxzID0gcXVhbnRpbGUoYmVzdF9vbmVzWyxuYW1lXSlbMjo0XQogICAgYmVzdF9tZWRpYW5zIDwtIHJiaW5kKGJlc3RfbWVkaWFucywgYyhpLCBuYW1lLCBiZXN0X3ZhbHNbMl0pKQogICAgbWVkaWFucyA8LSByYmluZChtZWRpYW5zLCBjKGksIG5hbWUsIHZhbHNbMl0pKQogIH0KfQoKY29sbmFtZXMobWVkaWFucykgPC1jKCJZZWFyIiwgIkZlYXR1cmUiLCAiVmFsdWUiKQpjb2xuYW1lcyhiZXN0X21lZGlhbnMpIDwtIGMoIlllYXIiLCAiRmVhdHVyZSIsICJWYWx1ZSIpCgpzcGxpdF9tZWRpYW5zIDwtIHNwbGl0KG1lZGlhbnMsIG1lZGlhbnMkRmVhdHVyZSkKc3BsaXRfYmVzdF9tZWRpYW5zIDwtIHNwbGl0KGJlc3RfbWVkaWFucywgYmVzdF9tZWRpYW5zJEZlYXR1cmUpCgpmZWF0dXJlX3F1YW50aWxlcyA9IG1hdHJpeCgwLCBuY29sPWxlbmd0aChmZWF0dXJlX25hbWVzKSwgbnJvdz0xMDApCmJlc3RfZmVhdHVyZV9xdWFudGlsZXMgPSBtYXRyaXgoMCwgbmNvbD1sZW5ndGgoZmVhdHVyZV9uYW1lcyksIG5yb3c9MTAwKQoKZm9yKGkgaW4gMTpsZW5ndGgoZmVhdHVyZV9uYW1lcykpewogIGZvcihqIGluIDE6MTAwKXsKICAgIGZlYXR1cmVfcXVhbnRpbGVzW2osaV0gPC0gc3BsaXRfbWVkaWFuc1tbaV1dW1szXV1bal0KICAgIGJlc3RfZmVhdHVyZV9xdWFudGlsZXNbaixpXSA9IHNwbGl0X2Jlc3RfbWVkaWFuc1tbaV1dW1szXV1bal0KICB9Cn0KCmRpbW5hbWVzKGZlYXR1cmVfcXVhbnRpbGVzKSA8LSBsaXN0KDE5MjE6MjAyMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZV9uYW1lcykKZGltbmFtZXMoYmVzdF9mZWF0dXJlX3F1YW50aWxlcykgPC0gbGlzdCgxOTIxOjIwMjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfbmFtZXMpCgoKCgpmb3IoaSBpbiAxOmxlbmd0aChmZWF0dXJlX25hbWVzKSl7CiAgbmFtZSA9IGZlYXR1cmVfbmFtZXNbaV0KICBzY29yZXMgPSBhcy5udW1lcmljKGZlYXR1cmVfcXVhbnRpbGVzWyxpXSkKICBiZXN0X3Njb3JlcyA9IGFzLm51bWVyaWMoYmVzdF9mZWF0dXJlX3F1YW50aWxlc1ssaV0pCiAgbWFwZSA9IG1hcGUoYmVzdF9zY29yZXMsIHNjb3JlcykKICBtYXBlID0gcm91bmQobWFwZSAqIDEwMCwzKQogIHIgID0gY29yKHNjb3JlcywgYmVzdF9zY29yZXMpCiAgcHZhbCA9IHQudGVzdChzY29yZXMsICkKICAjIE5vcm1hbGl6ZSBpdD8/Pz8/IC8gbWFrZSBpdCBhIGZ1bmN0aW9uIG9mIGl0cyBvd24gbWVhbgogIAogIHNtYWxsZXN0ID0gbWluKG1pbihzY29yZXMpLCBtaW4oYmVzdF9zY29yZXMpKQogIGJpZ2dlc3QgPSBtYXgobWF4KHNjb3JlcyksIG1heChiZXN0X3Njb3JlcykpCiAgCiAgcGxvdCh4PTE5MjE6MjAyMCwgeT1zY29yZXMsIGNvbD0xLCB0eXBlPSJsIiwgeWxpbT1jKHNtYWxsZXN0LCBiaWdnZXN0KSwgbWFpbj1uYW1lLCBzdWI9cGFzdGUoIk1BUEU6ICIsIG1hcGUsICIlOyIsICJSOiIsIHIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKICBsaW5lcyh4PTE5MjE6MjAyMCwgeT1iZXN0X3Njb3JlcywgY29sPTIpCiAgbGVnZW5kKCJ0b3ByaWdodCIsIGx0eT0xLCBjb2w9YygxLDIpLCBsZWdlbmQ9YygiT3ZlcmFsbCIsICJNb3N0IFBvcHVsYXIiKSkKICB0aXRsZSgpCn0KCmBgYAoKCgpUcmFpbiB0aW1lIHNlcmllcyBtb2RlbHMgdG8gZm9yZWNhc3QgdGhlIGZ1dHVyZSBtb2RlbHMuClNldCB1cCB0aGUgbGlicmFyaWVzIGFuZCB0aGUgdHJhaW5pbmcvdGVzdGluZyBhbW91bnRzCmBgYHtyfQpsaWJyYXJ5KCJzbW9vdGgiKQpsaWJyYXJ5KCJmb3JlY2FzdCIpCmxpYnJhcnkoIm5uZm9yIikKdHJhaW5pbmcucGVyY2VudCA9IDAuOTUKblRyYWluID0gMTAwKnRyYWluaW5nLnBlcmNlbnQKblRlc3QgPSAxMDAqKDEtdHJhaW5pbmcucGVyY2VudCkKYGBgCgpBY2NvdXN0aWNuZXNzIE1vZGVscwpgYGB7cn0KYWNvdXN0aWNuZXNzID0gdHMoYXMubnVtZXJpYyhmZWF0dXJlX3F1YW50aWxlc1ssImFjb3VzdGljbmVzcyJdKSAsIHN0YXJ0PTE5MjEpCmFjb3VzdGljbmVzcy50cmFpbiA9IHN1YnNldChhY291c3RpY25lc3MsIHN0YXJ0PTEsIGVuZD1uVHJhaW4pCmFjb3VzdGljbmVzcy50ZXN0ID0gc3Vic2V0KGFjb3VzdGljbmVzcywgc3RhcnQgPSAoblRyYWluKzEpLCBlbmQgPShuVHJhaW4rblRlc3QpKQoKI3NlcwphY291c3RpY25lc3MudHJhaW4uc2VzIDwtIHNlcyhhY291c3RpY25lc3MudHJhaW4sIGg9blRlc3QpCmFjb3VzdGljbmVzcy5zZXMubWFwZSA9IG1hcGUoYWNvdXN0aWNuZXNzLnRyYWluLnNlcyRtZWFuLCBhY291c3RpY25lc3MudGVzdCkKcGxvdChhY291c3RpY25lc3MudHJhaW4uc2VzLCBtYWluPXBhc3RlKCJBY291c3RpY25lc3MiLCAiU0VTIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZChhY291c3RpY25lc3Muc2VzLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKGFjb3VzdGljbmVzcykKCiNTQVJJTUEKYWNvdXN0aWNuZXNzLnNhcmltYS5tb2RlbCA8LSBhdXRvLmFyaW1hKGFjb3VzdGljbmVzcy50cmFpbikKYWNvdXN0aWNuZXNzLnNhcmltYSA8LSBmb3JlY2FzdChhY291c3RpY25lc3Muc2FyaW1hLm1vZGVsLCBoPW5UZXN0KQphY291c3RpY25lc3Muc2FyaW1hLm1hcGUgPSBtYXBlKGFjb3VzdGljbmVzcy5zYXJpbWEkbWVhbiwgYWNvdXN0aWNuZXNzLnRlc3QpCnBsb3QoYWNvdXN0aWNuZXNzLnNhcmltYSwgbWFpbj1wYXN0ZSgiQWNvdXN0aWNuZXNzIiwgIlNBUklNQSIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoYWNvdXN0aWNuZXNzLnNhcmltYS5tYXBlKjEwMCwzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKGFjb3VzdGljbmVzcykKCiNOZXVyYWwgTmV0d29yawphY291c3RpY25lc3MudHJhaW4ubm4gPC0gZWxtKGFjb3VzdGljbmVzcy50cmFpbikKYWNvdXN0aWNuZXNzLm5uLmZvcmVjYXN0IDwtIGZvcmVjYXN0KGFjb3VzdGljbmVzcy50cmFpbi5ubiwgaD1uVGVzdCkKYWNvdXN0aWNuZXNzLm5uLm1hcGUgPSBtYXBlKGFjb3VzdGljbmVzcy5ubi5mb3JlY2FzdCRtZWFuLCBhY291c3RpY25lc3MudGVzdCkKcGxvdChhY291c3RpY25lc3MsIG1haW49cGFzdGUoIkFjb3VzdGljbmVzcyIsICJOTiIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoYWNvdXN0aWNuZXNzLm5uLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKHRzKGFjb3VzdGljbmVzcy5ubi5mb3JlY2FzdCRtZWFuLCBzdGFydD0xOTIxK25UcmFpbiksIGNvbD0yKQpgYGAKCkVuZXJneQpgYGB7cn0KZW5lcmd5ID0gdHMoYXMubnVtZXJpYyhmZWF0dXJlX3F1YW50aWxlc1ssImVuZXJneSJdKSAsIHN0YXJ0PTE5MjEpCmVuZXJneS50cmFpbiA9IHN1YnNldChlbmVyZ3ksIHN0YXJ0PTEsIGVuZD1uVHJhaW4pCmVuZXJneS50ZXN0ID0gc3Vic2V0KGVuZXJneSwgc3RhcnQgPSAoblRyYWluKzEpLCBlbmQgPShuVHJhaW4rblRlc3QpKQoKI1NFUwplbmVyZ3kudHJhaW4uc2VzIDwtIHNlcyhlbmVyZ3kudHJhaW4sIGg9blRlc3QpCmVuZXJneS5zZXMubWFwZSA9IG1hcGUoZW5lcmd5LnRyYWluLnNlcyRtZWFuLCBlbmVyZ3kudGVzdCkKcGxvdChlbmVyZ3kudHJhaW4uc2VzLCBtYWluPXBhc3RlKCJFbmVyZ3kiLCAiU0VTIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZChlbmVyZ3kuc2VzLm1hcGUqIDEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhlbmVyZ3kpCgojU0FSSU1BCmVuZXJneS5zYXJpbWEubW9kZWwgPC0gYXV0by5hcmltYShlbmVyZ3kudHJhaW4pCmVuZXJneS5zYXJpbWEgPC0gZm9yZWNhc3QoZW5lcmd5LnNhcmltYS5tb2RlbCwgaD1uVGVzdCkKZW5lcmd5LnNhcmltYS5tYXBlID0gbWFwZShlbmVyZ3kuc2FyaW1hJG1lYW4sIGVuZXJneS50ZXN0KQpwbG90KGVuZXJneS5zYXJpbWEsIG1haW49cGFzdGUoIkVuZXJneSIsICJTQVJJTUEiKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGVuZXJneS5zYXJpbWEubWFwZSogMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKGVuZXJneSkKCiNOZXVyYWwgTmV0d29yawplbmVyZ3kudHJhaW4ubm4gPC0gZWxtKGVuZXJneS50cmFpbikKZW5lcmd5Lm5uLmZvcmVjYXN0IDwtIGZvcmVjYXN0KGVuZXJneS50cmFpbi5ubiwgaD1uVGVzdCkKZW5lcmd5Lm5uLm1hcGUgPSBtYXBlKGVuZXJneS5ubi5mb3JlY2FzdCRtZWFuLCBlbmVyZ3kudGVzdCkKcGxvdChlbmVyZ3ksIG1haW49cGFzdGUoIkVuZXJneSIsICJOTiIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoZW5lcmd5Lm5uLm1hcGUqIDEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh0cyhlbmVyZ3kubm4uZm9yZWNhc3QkbWVhbiwgc3RhcnQ9MTkyMStuVHJhaW4pLCBjb2w9MikKYGBgCgpEYW5jZWFiaWxpdHkKYGBge3J9CmRhbmNlYWJpbGl0eSA9IHRzKGFzLm51bWVyaWMoZmVhdHVyZV9xdWFudGlsZXNbLCJkYW5jZWFiaWxpdHkiXSkgLCBzdGFydD0xOTIxKQpkYW5jZWFiaWxpdHkudHJhaW4gPSBzdWJzZXQoZGFuY2VhYmlsaXR5LCBzdGFydD0xLCBlbmQ9blRyYWluKQpkYW5jZWFiaWxpdHkudGVzdCA9IHN1YnNldChkYW5jZWFiaWxpdHksIHN0YXJ0ID0gKG5UcmFpbisxKSwgZW5kID0oblRyYWluK25UZXN0KSkKCiNTRVMKZGFuY2VhYmlsaXR5LnRyYWluLnNlcyA8LSBzZXMoZGFuY2VhYmlsaXR5LnRyYWluLCBoPW5UZXN0KQpkYW5jZWFiaWxpdHkuc2VzLm1hcGUgPSBtYXBlKGRhbmNlYWJpbGl0eS50cmFpbi5zZXMkbWVhbiwgZGFuY2VhYmlsaXR5LnRlc3QpCnBsb3QoZGFuY2VhYmlsaXR5LnRyYWluLnNlcywgbWFpbj1wYXN0ZSgiZGFuY2VhYmlsaXR5IiwgIlNFUyIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoZGFuY2VhYmlsaXR5LnNlcy5tYXBlKiAxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXMoZGFuY2VhYmlsaXR5KQoKI1NBUklNQQpkYW5jZWFiaWxpdHkuc2FyaW1hLm1vZGVsIDwtIGF1dG8uYXJpbWEoZGFuY2VhYmlsaXR5LnRyYWluKQpkYW5jZWFiaWxpdHkuc2FyaW1hIDwtIGZvcmVjYXN0KGRhbmNlYWJpbGl0eS5zYXJpbWEubW9kZWwsIGg9blRlc3QpCmRhbmNlYWJpbGl0eS5zYXJpbWEubWFwZSA9IG1hcGUoZGFuY2VhYmlsaXR5LnNhcmltYSRtZWFuLCBkYW5jZWFiaWxpdHkudGVzdCkKcGxvdChkYW5jZWFiaWxpdHkuc2FyaW1hLCBtYWluPXBhc3RlKCJkYW5jZWFiaWxpdHkiLCAiU0FSSU1BIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZChkYW5jZWFiaWxpdHkuc2FyaW1hLm1hcGUqIDEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhkYW5jZWFiaWxpdHkpCgojTmV1cmFsIE5ldHdvcmsKZGFuY2VhYmlsaXR5LnRyYWluLm5uIDwtIGVsbShkYW5jZWFiaWxpdHkudHJhaW4pCmRhbmNlYWJpbGl0eS5ubi5mb3JlY2FzdCA8LSBmb3JlY2FzdChkYW5jZWFiaWxpdHkudHJhaW4ubm4sIGg9blRlc3QpCmRhbmNlYWJpbGl0eS5ubi5tYXBlID0gbWFwZShkYW5jZWFiaWxpdHkubm4uZm9yZWNhc3QkbWVhbiwgZGFuY2VhYmlsaXR5LnRlc3QpCnBsb3QoZGFuY2VhYmlsaXR5LCBtYWluPXBhc3RlKCJkYW5jZWFiaWxpdHkiLCAiTk4iKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGRhbmNlYWJpbGl0eS5ubi5tYXBlKiAxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXModHMoZGFuY2VhYmlsaXR5Lm5uLmZvcmVjYXN0JG1lYW4sIHN0YXJ0PTE5MjErblRyYWluKSwgY29sPTIpCmBgYAoKCkR1cmF0aW9uCmBgYHtyfQpkdXJhdGlvbiA9IHRzKGFzLm51bWVyaWMoZmVhdHVyZV9xdWFudGlsZXNbLCJkdXJhdGlvbl9tcyJdKSAsIHN0YXJ0PTE5MjEpCmR1cmF0aW9uLnRyYWluID0gc3Vic2V0KGR1cmF0aW9uLCBzdGFydD0xLCBlbmQ9blRyYWluKQpkdXJhdGlvbi50ZXN0ID0gc3Vic2V0KGR1cmF0aW9uLCBzdGFydCA9IChuVHJhaW4rMSksIGVuZCA9KG5UcmFpbituVGVzdCkpCgojU0VTCmR1cmF0aW9uLnRyYWluLnNlcyA8LSBzZXMoZHVyYXRpb24udHJhaW4sIGg9blRlc3QpCmR1cmF0aW9uLnNlcy5tYXBlID0gbWFwZShkdXJhdGlvbi50cmFpbi5zZXMkbWVhbiwgZHVyYXRpb24udGVzdCkKcGxvdChkdXJhdGlvbi50cmFpbi5zZXMsIG1haW49cGFzdGUoIkR1cmF0aW9uIiwgIlNFUyIpLCBzdWI9cGFzdGUoIk1BUEU6Iixyb3VuZChkdXJhdGlvbi5zZXMubWFwZSoxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXMoZHVyYXRpb24pCgojU0FSSU1BCmR1cmF0aW9uLnNhcmltYS5tb2RlbCA8LSBhdXRvLmFyaW1hKGR1cmF0aW9uLnRyYWluKQpkdXJhdGlvbi5zYXJpbWEgPC0gZm9yZWNhc3QoZHVyYXRpb24uc2FyaW1hLm1vZGVsLCBoPW5UZXN0KQpkdXJhdGlvbi5zYXJpbWEubWFwZSA9IG1hcGUoZHVyYXRpb24uc2FyaW1hJG1lYW4sIGR1cmF0aW9uLnRlc3QpCnBsb3QoZHVyYXRpb24uc2FyaW1hLCBtYWluPXBhc3RlKCJEdXJhdGlvbiIsICJTQVJJTUEiKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGR1cmF0aW9uLnNhcmltYS5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhkdXJhdGlvbikKCgojTmV1cmFsIE5ldHdvcmsKZHVyYXRpb24udHJhaW4ubm4gPC0gZWxtKGR1cmF0aW9uLnRyYWluKQpkdXJhdGlvbi5ubi5mb3JlY2FzdCA8LSBmb3JlY2FzdChkdXJhdGlvbi50cmFpbi5ubiwgaD1uVGVzdCkKZHVyYXRpb24ubm4ubWFwZSA9IG1hcGUoZHVyYXRpb24ubm4uZm9yZWNhc3QkbWVhbiwgZHVyYXRpb24udGVzdCkKcGxvdChkdXJhdGlvbiwgbWFpbj1wYXN0ZSgiRHVyYXRpb24iLCAiTk4iKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGR1cmF0aW9uLm5uLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKHRzKGR1cmF0aW9uLm5uLmZvcmVjYXN0JG1lYW4sIHN0YXJ0PTE5MjErblRyYWluKSwgY29sPTIpCmBgYAoKVmFsZW5jZQpgYGB7cn0KdmFsZW5jZSA9IHRzKGFzLm51bWVyaWMoZmVhdHVyZV9xdWFudGlsZXNbLCJ2YWxlbmNlIl0pICwgc3RhcnQ9MTkyMSkKdmFsZW5jZS50cmFpbiA9IHN1YnNldCh2YWxlbmNlLCBzdGFydD0xLCBlbmQ9blRyYWluKQp2YWxlbmNlLnRlc3QgPSBzdWJzZXQodmFsZW5jZSwgc3RhcnQgPSAoblRyYWluKzEpLCBlbmQgPShuVHJhaW4rblRlc3QpKQoKI1NFUwp2YWxlbmNlLnRyYWluLnNlcyA8LSBzZXModmFsZW5jZS50cmFpbiwgaD1uVGVzdCkKdmFsZW5jZS5zZXMubWFwZSA9IG1hcGUodmFsZW5jZS50cmFpbi5zZXMkbWVhbiwgdmFsZW5jZS50ZXN0KQpwbG90KHZhbGVuY2UudHJhaW4uc2VzLCBtYWluPXBhc3RlKCJ2YWxlbmNlIiwgIlNFUyIpLCBzdWI9cGFzdGUoIk1BUEU6Iixyb3VuZCh2YWxlbmNlLnNlcy5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh2YWxlbmNlKQoKI1NBUklNQQp2YWxlbmNlLnNhcmltYS5tb2RlbCA8LSBhdXRvLmFyaW1hKHZhbGVuY2UudHJhaW4pCnZhbGVuY2Uuc2FyaW1hIDwtIGZvcmVjYXN0KHZhbGVuY2Uuc2FyaW1hLm1vZGVsLCBoPW5UZXN0KQp2YWxlbmNlLnNhcmltYS5tYXBlID0gbWFwZSh2YWxlbmNlLnNhcmltYSRtZWFuLCB2YWxlbmNlLnRlc3QpCnBsb3QodmFsZW5jZS5zYXJpbWEsIG1haW49cGFzdGUoInZhbGVuY2UiLCAiU0FSSU1BIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZCh2YWxlbmNlLnNhcmltYS5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh2YWxlbmNlKQoKCiNOZXVyYWwgTmV0d29yawp2YWxlbmNlLnRyYWluLm5uIDwtIGVsbSh2YWxlbmNlLnRyYWluKQp2YWxlbmNlLm5uLmZvcmVjYXN0IDwtIGZvcmVjYXN0KHZhbGVuY2UudHJhaW4ubm4sIGg9blRlc3QpCnZhbGVuY2Uubm4ubWFwZSA9IG1hcGUodmFsZW5jZS5ubi5mb3JlY2FzdCRtZWFuLCB2YWxlbmNlLnRlc3QpCnBsb3QodmFsZW5jZSwgbWFpbj1wYXN0ZSgidmFsZW5jZSIsICJOTiIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQodmFsZW5jZS5ubi5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh0cyh2YWxlbmNlLm5uLmZvcmVjYXN0JG1lYW4sIHN0YXJ0PTE5MjErblRyYWluKSwgY29sPTIpCmBgYAoKVGVtcG8KYGBge3J9CnRlbXBvID0gdHMoYXMubnVtZXJpYyhmZWF0dXJlX3F1YW50aWxlc1ssInRlbXBvIl0pICwgc3RhcnQ9MTkyMSkKdGVtcG8udHJhaW4gPSBzdWJzZXQodGVtcG8sIHN0YXJ0PTEsIGVuZD1uVHJhaW4pCnRlbXBvLnRlc3QgPSBzdWJzZXQodGVtcG8sIHN0YXJ0ID0gKG5UcmFpbisxKSwgZW5kID0oblRyYWluK25UZXN0KSkKCiNTRVMKdGVtcG8udHJhaW4uc2VzIDwtIHNlcyh0ZW1wby50cmFpbiwgaD1uVGVzdCkKdGVtcG8uc2VzLm1hcGUgPSBtYXBlKHRlbXBvLnRyYWluLnNlcyRtZWFuLCB0ZW1wby50ZXN0KQpwbG90KHRlbXBvLnRyYWluLnNlcywgbWFpbj1wYXN0ZSgidGVtcG8iLCAiU0VTIiksIHN1Yj1wYXN0ZSgiTUFQRToiLHJvdW5kKHRlbXBvLnNlcy5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh0ZW1wbykKCiNTQVJJTUEKdGVtcG8uc2FyaW1hLm1vZGVsIDwtIGF1dG8uYXJpbWEodGVtcG8udHJhaW4pCnRlbXBvLnNhcmltYSA8LSBmb3JlY2FzdCh0ZW1wby5zYXJpbWEubW9kZWwsIGg9blRlc3QpCnRlbXBvLnNhcmltYS5tYXBlID0gbWFwZSh0ZW1wby5zYXJpbWEkbWVhbiwgdGVtcG8udGVzdCkKcGxvdCh0ZW1wby5zYXJpbWEsIG1haW49cGFzdGUoInRlbXBvIiwgIlNBUklNQSIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQodGVtcG8uc2FyaW1hLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKHRlbXBvKQoKCiNOZXVyYWwgTmV0d29yawp0ZW1wby50cmFpbi5ubiA8LSBlbG0odGVtcG8udHJhaW4pCnRlbXBvLm5uLmZvcmVjYXN0IDwtIGZvcmVjYXN0KHRlbXBvLnRyYWluLm5uLCBoPW5UZXN0KQp0ZW1wby5ubi5tYXBlID0gbWFwZSh0ZW1wby5ubi5mb3JlY2FzdCRtZWFuLCB0ZW1wby50ZXN0KQpwbG90KHRlbXBvLCBtYWluPXBhc3RlKCJ0ZW1wbyIsICJOTiIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQodGVtcG8ubm4ubWFwZSoxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXModHModGVtcG8ubm4uZm9yZWNhc3QkbWVhbiwgc3RhcnQ9MTkyMStuVHJhaW4pLCBjb2w9MikKYGBgCgpMaXZlbmVzcwpgYGB7cn0KbGl2ZW5lc3MgPSB0cyhhcy5udW1lcmljKGZlYXR1cmVfcXVhbnRpbGVzWywibGl2ZW5lc3MiXSkgLCBzdGFydD0xOTIxKQpsaXZlbmVzcy50cmFpbiA9IHN1YnNldChsaXZlbmVzcywgc3RhcnQ9MSwgZW5kPW5UcmFpbikKbGl2ZW5lc3MudGVzdCA9IHN1YnNldChsaXZlbmVzcywgc3RhcnQgPSAoblRyYWluKzEpLCBlbmQgPShuVHJhaW4rblRlc3QpKQoKI1NFUwpsaXZlbmVzcy50cmFpbi5zZXMgPC0gc2VzKGxpdmVuZXNzLnRyYWluLCBoPW5UZXN0KQpsaXZlbmVzcy5zZXMubWFwZSA9IG1hcGUobGl2ZW5lc3MudHJhaW4uc2VzJG1lYW4sIGxpdmVuZXNzLnRlc3QpCnBsb3QobGl2ZW5lc3MudHJhaW4uc2VzLCBtYWluPXBhc3RlKCJsaXZlbmVzcyIsICJTRVMiKSwgc3ViPXBhc3RlKCJNQVBFOiIscm91bmQobGl2ZW5lc3Muc2VzLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKGxpdmVuZXNzKQoKI1NBUklNQQpsaXZlbmVzcy5zYXJpbWEubW9kZWwgPC0gYXV0by5hcmltYShsaXZlbmVzcy50cmFpbikKbGl2ZW5lc3Muc2FyaW1hIDwtIGZvcmVjYXN0KGxpdmVuZXNzLnNhcmltYS5tb2RlbCwgaD1uVGVzdCkKbGl2ZW5lc3Muc2FyaW1hLm1hcGUgPSBtYXBlKGxpdmVuZXNzLnNhcmltYSRtZWFuLCBsaXZlbmVzcy50ZXN0KQpwbG90KGxpdmVuZXNzLnNhcmltYSwgbWFpbj1wYXN0ZSgibGl2ZW5lc3MiLCAiU0FSSU1BIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZChsaXZlbmVzcy5zYXJpbWEubWFwZSoxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXMobGl2ZW5lc3MpCgoKI05ldXJhbCBOZXR3b3JrCmxpdmVuZXNzLnRyYWluLm5uIDwtIGVsbShsaXZlbmVzcy50cmFpbikKbGl2ZW5lc3Mubm4uZm9yZWNhc3QgPC0gZm9yZWNhc3QobGl2ZW5lc3MudHJhaW4ubm4sIGg9blRlc3QpCmxpdmVuZXNzLm5uLm1hcGUgPSBtYXBlKGxpdmVuZXNzLm5uLmZvcmVjYXN0JG1lYW4sIGxpdmVuZXNzLnRlc3QpCnBsb3QobGl2ZW5lc3MsIG1haW49cGFzdGUoImxpdmVuZXNzIiwgIk5OIiksIHN1Yj1wYXN0ZSgiTUFQRToiLCByb3VuZChsaXZlbmVzcy5ubi5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh0cyhsaXZlbmVzcy5ubi5mb3JlY2FzdCRtZWFuLCBzdGFydD0xOTIxK25UcmFpbiksIGNvbD0yKQpgYGAKCkxvdWRuZXNzCmBgYHtyfQpsb3VkbmVzcyA9IHRzKGFzLm51bWVyaWMoZmVhdHVyZV9xdWFudGlsZXNbLCJsb3VkbmVzcyJdKSAsIHN0YXJ0PTE5MjEpCmxvdWRuZXNzLnRyYWluID0gc3Vic2V0KGxvdWRuZXNzLCBzdGFydD0xLCBlbmQ9blRyYWluKQpsb3VkbmVzcy50ZXN0ID0gc3Vic2V0KGxvdWRuZXNzLCBzdGFydCA9IChuVHJhaW4rMSksIGVuZCA9KG5UcmFpbituVGVzdCkpCgojU0VTCmxvdWRuZXNzLnRyYWluLnNlcyA8LSBzZXMobG91ZG5lc3MudHJhaW4sIGg9blRlc3QpCmxvdWRuZXNzLnNlcy5tYXBlID0gbWFwZShsb3VkbmVzcy50cmFpbi5zZXMkbWVhbiwgbG91ZG5lc3MudGVzdCkKcGxvdChsb3VkbmVzcy50cmFpbi5zZXMsIG1haW49cGFzdGUoImxvdWRuZXNzIiwgIlNFUyIpLCBzdWI9cGFzdGUoIk1BUEU6Iixyb3VuZChsb3VkbmVzcy5zZXMubWFwZSoxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXMobG91ZG5lc3MpCgojU0FSSU1BCmxvdWRuZXNzLnNhcmltYS5tb2RlbCA8LSBhdXRvLmFyaW1hKGxvdWRuZXNzLnRyYWluKQpsb3VkbmVzcy5zYXJpbWEgPC0gZm9yZWNhc3QobG91ZG5lc3Muc2FyaW1hLm1vZGVsLCBoPW5UZXN0KQpsb3VkbmVzcy5zYXJpbWEubWFwZSA9IG1hcGUobG91ZG5lc3Muc2FyaW1hJG1lYW4sIGxvdWRuZXNzLnRlc3QpCnBsb3QobG91ZG5lc3Muc2FyaW1hLCBtYWluPXBhc3RlKCJsb3VkbmVzcyIsICJTQVJJTUEiKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGxvdWRuZXNzLnNhcmltYS5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhsb3VkbmVzcykKCgojTmV1cmFsIE5ldHdvcmsKbG91ZG5lc3MudHJhaW4ubm4gPC0gZWxtKGxvdWRuZXNzLnRyYWluKQpsb3VkbmVzcy5ubi5mb3JlY2FzdCA8LSBmb3JlY2FzdChsb3VkbmVzcy50cmFpbi5ubiwgaD1uVGVzdCkKbG91ZG5lc3Mubm4ubWFwZSA9IG1hcGUobG91ZG5lc3Mubm4uZm9yZWNhc3QkbWVhbiwgbG91ZG5lc3MudGVzdCkKcGxvdChsb3VkbmVzcywgbWFpbj1wYXN0ZSgibG91ZG5lc3MiLCAiTk4iKSwgc3ViPXBhc3RlKCJNQVBFOiIsIHJvdW5kKGxvdWRuZXNzLm5uLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKHRzKGxvdWRuZXNzLm5uLmZvcmVjYXN0JG1lYW4sIHN0YXJ0PTE5MjErblRyYWluKSwgY29sPTIpCmBgYAoKU3BlZWNoaW5lc3MKYGBge3J9CnNwZWVjaGluZXNzID0gdHMoYXMubnVtZXJpYyhmZWF0dXJlX3F1YW50aWxlc1ssInNwZWVjaGluZXNzIl0pICwgc3RhcnQ9MTkyMSkKc3BlZWNoaW5lc3MudHJhaW4gPSBzdWJzZXQoc3BlZWNoaW5lc3MsIHN0YXJ0PTEsIGVuZD1uVHJhaW4pCnNwZWVjaGluZXNzLnRlc3QgPSBzdWJzZXQoc3BlZWNoaW5lc3MsIHN0YXJ0ID0gKG5UcmFpbisxKSwgZW5kID0oblRyYWluK25UZXN0KSkKCiNTRVMKc3BlZWNoaW5lc3MudHJhaW4uc2VzIDwtIHNlcyhzcGVlY2hpbmVzcy50cmFpbiwgaD1uVGVzdCkKc3BlZWNoaW5lc3Muc2VzLm1hcGUgPSBtYXBlKHNwZWVjaGluZXNzLnRyYWluLnNlcyRtZWFuLCBzcGVlY2hpbmVzcy50ZXN0KQpwbG90KHNwZWVjaGluZXNzLnRyYWluLnNlcywgbWFpbj1wYXN0ZSgic3BlZWNoaW5lc3MiLCAiU0VTIiksIHN1Yj1wYXN0ZSgiTUFQRToiLHJvdW5kKHNwZWVjaGluZXNzLnNlcy5tYXBlKjEwMCwgMyksICIlIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhzcGVlY2hpbmVzcykKCiNTQVJJTUEKc3BlZWNoaW5lc3Muc2FyaW1hLm1vZGVsIDwtIGF1dG8uYXJpbWEoc3BlZWNoaW5lc3MudHJhaW4pCnNwZWVjaGluZXNzLnNhcmltYSA8LSBmb3JlY2FzdChzcGVlY2hpbmVzcy5zYXJpbWEubW9kZWwsIGg9blRlc3QpCnNwZWVjaGluZXNzLnNhcmltYS5tYXBlID0gbWFwZShzcGVlY2hpbmVzcy5zYXJpbWEkbWVhbiwgc3BlZWNoaW5lc3MudGVzdCkKcGxvdChzcGVlY2hpbmVzcy5zYXJpbWEsIG1haW49cGFzdGUoInNwZWVjaGluZXNzIiwgIlNBUklNQSIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoc3BlZWNoaW5lc3Muc2FyaW1hLm1hcGUqMTAwLCAzKSwgIiUiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKHNwZWVjaGluZXNzKQoKCiNOZXVyYWwgTmV0d29yawpzcGVlY2hpbmVzcy50cmFpbi5ubiA8LSBlbG0oc3BlZWNoaW5lc3MudHJhaW4pCnNwZWVjaGluZXNzLm5uLmZvcmVjYXN0IDwtIGZvcmVjYXN0KHNwZWVjaGluZXNzLnRyYWluLm5uLCBoPW5UZXN0KQpzcGVlY2hpbmVzcy5ubi5tYXBlID0gbWFwZShzcGVlY2hpbmVzcy5ubi5mb3JlY2FzdCRtZWFuLCBzcGVlY2hpbmVzcy50ZXN0KQpwbG90KHNwZWVjaGluZXNzLCBtYWluPXBhc3RlKCJzcGVlY2hpbmVzcyIsICJOTiIpLCBzdWI9cGFzdGUoIk1BUEU6Iiwgcm91bmQoc3BlZWNoaW5lc3Mubm4ubWFwZSoxMDAsIDMpLCAiJSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXModHMoc3BlZWNoaW5lc3Mubm4uZm9yZWNhc3QkbWVhbiwgc3RhcnQ9MTkyMStuVHJhaW4pLCBjb2w9MikKYGBgCgpDcmVhdGluZyBsaW5lYXIgbW9kZWxzIGZvciBwcmVkaWN0aW5nIHBvcHVsYXIgZmVhdHVyZXMgYmFzZWQgb24gdGhlaXIgY3VycmVudCBmZWF0dXJlCmBgYHtyfQojRW5lcmd5CmJlc3RfZW5lcmd5ID0gdHMoYXMubnVtZXJpYyhiZXN0X2ZlYXR1cmVfcXVhbnRpbGVzWywiZW5lcmd5Il0pLCBzdGFydD0xOTIxLCBlbmQ9MjAyMCkgCmJlc3RfZW5lcmd5LnRyYWluID0gc3Vic2V0KGJlc3RfZW5lcmd5LCBzdGFydCA9IDEsIGVuZCA9IG5UcmFpbikKYmVzdF9lbmVyZ3kudGVzdCA9IHN1YnNldChiZXN0X2VuZXJneSwgc3RhcnQgPSBuVHJhaW4rMSwgZW5kID0gblRyYWluK25UZXN0KQp0cmFpbl9kZiA9IGRhdGEuZnJhbWUoYmVzdCA9IGJlc3RfZW5lcmd5LnRyYWluLCBvdmVyYWxsID0gZW5lcmd5LnRyYWluKQoKYmVzdCA9IGJlc3RfZW5lcmd5LnRyYWluCm92ZXJhbGwgPSBlbmVyZ3kudHJhaW4KCmVuZXJneS5sbSA8LSBsbShiZXN0IH4gb3ZlcmFsbCkKdGVzdF9kZiA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSBlbmVyZ3kudGVzdCkKYmVzdF9lbmVyZ3kudGVzdC5wcmVkaWN0IDwtIHByZWRpY3QoZW5lcmd5LmxtLCBuZXdkYXRhPXRlc3RfZGYsIGludGVydmFsPSJwcmVkaWN0aW9uIikKYmVzdF9lbmVyZ3lfdGVzdF9wcmVkaWN0Lm1hcGUgPC0gbWFwZShiZXN0X2VuZXJneS50ZXN0WzE6bGVuZ3RoKGJlc3RfZW5lcmd5LnRlc3QpXSwgYmVzdF9lbmVyZ3kudGVzdC5wcmVkaWN0WywxXSkKcGxvdChiZXN0X2VuZXJneSx4bGFiPSJZZWFyIiwgeWxhYj0iRW5lcmd5IiwgbWFpbj1wYXN0ZSgiRW5lcmd5IFBvcHVsYXRpb24gUHJlZGljdGlvbiIpLCBzdWIgPSBwYXN0ZSgiTUFQRTogIiwgcm91bmQoYmVzdF9lbmVyZ3lfdGVzdF9wcmVkaWN0Lm1hcGUqMTAwLCAzKSwgIiUiKSkKbGluZXModHMoYmVzdF9lbmVyZ3kudGVzdC5wcmVkaWN0WywxXSwgc3RhcnQgPSAxOTIxK25UcmFpbisxLCBlbmQgPSAyMDIwKSwgY29sID0gMikKCiNEdXJhdGlvbgpiZXN0X2R1cmF0aW9uID0gdHMoYXMubnVtZXJpYyhiZXN0X2ZlYXR1cmVfcXVhbnRpbGVzWywiZHVyYXRpb25fbXMiXSksIHN0YXJ0PTE5MjEsIGVuZD0yMDIwKSAKYmVzdF9kdXJhdGlvbi50cmFpbiA9IHN1YnNldChiZXN0X2R1cmF0aW9uLCBzdGFydCA9IDEsIGVuZCA9IG5UcmFpbikKYmVzdF9kdXJhdGlvbi50ZXN0ID0gc3Vic2V0KGJlc3RfZHVyYXRpb24sIHN0YXJ0ID0gblRyYWluKzEsIGVuZCA9IG5UcmFpbituVGVzdCkKdHJhaW5fZGYgPSBkYXRhLmZyYW1lKGJlc3QgPSBiZXN0X2R1cmF0aW9uLnRyYWluLCBvdmVyYWxsID0gZHVyYXRpb24udHJhaW4pCgpiZXN0ID0gYmVzdF9kdXJhdGlvbi50cmFpbgpvdmVyYWxsID0gZHVyYXRpb24udHJhaW4KCmR1cmF0aW9uLmxtIDwtIGxtKGJlc3QgfiBvdmVyYWxsKQp0ZXN0X2RmIDwtIGRhdGEuZnJhbWUob3ZlcmFsbCA9IGR1cmF0aW9uLnRlc3QpCmJlc3RfZHVyYXRpb24udGVzdC5wcmVkaWN0IDwtIHByZWRpY3QoZHVyYXRpb24ubG0sIG5ld2RhdGE9dGVzdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQpiZXN0X2R1cmF0aW9uX3Rlc3QucHJlZGljdC5tYXBlIDwtIG1hcGUoYmVzdF9kdXJhdGlvbi50ZXN0WzE6bGVuZ3RoKGJlc3RfZHVyYXRpb24udGVzdCldLCBiZXN0X2R1cmF0aW9uLnRlc3QucHJlZGljdFssMV0pCnBsb3QoYmVzdF9kdXJhdGlvbix4bGFiPSJZZWFyIiwgeWxhYj0iZHVyYXRpb24iLCBtYWluPXBhc3RlKCJkdXJhdGlvbiBQb3B1bGFyaXR5IFByZWRpY3Rpb24iKSwgc3ViID0gcGFzdGUoIk1BUEU6ICIsIHJvdW5kKGJlc3RfZHVyYXRpb25fdGVzdC5wcmVkaWN0Lm1hcGUqMTAwLCAzKSwgIiUiKSkKbGluZXModHMoYmVzdF9kdXJhdGlvbi50ZXN0LnByZWRpY3RbLDFdLCBzdGFydCA9IDE5MjErblRyYWluKzEsIGVuZCA9IDIwMjApLCBjb2wgPSAyKQoKI0FjY291c3RpY25lc3MKYmVzdF9hY291c3RpY25lc3MgPSB0cyhhcy5udW1lcmljKGJlc3RfZmVhdHVyZV9xdWFudGlsZXNbLCJhY291c3RpY25lc3MiXSksIHN0YXJ0PTE5MjEsIGVuZD0yMDIwKSAKYmVzdF9hY291c3RpY25lc3MudHJhaW4gPSBzdWJzZXQoYmVzdF9hY291c3RpY25lc3MsIHN0YXJ0ID0gMSwgZW5kID0gblRyYWluKQpiZXN0X2Fjb3VzdGljbmVzcy50ZXN0ID0gc3Vic2V0KGJlc3RfYWNvdXN0aWNuZXNzLCBzdGFydCA9IG5UcmFpbisxLCBlbmQgPSBuVHJhaW4rblRlc3QpCnRyYWluX2RmID0gZGF0YS5mcmFtZShiZXN0ID0gYmVzdF9hY291c3RpY25lc3MudHJhaW4sIG92ZXJhbGwgPSBhY291c3RpY25lc3MudHJhaW4pCgpiZXN0ID0gYmVzdF9hY291c3RpY25lc3MudHJhaW4Kb3ZlcmFsbCA9IGFjb3VzdGljbmVzcy50cmFpbgoKYWNvdXN0aWNuZXNzLmxtIDwtIGxtKGJlc3QgfiBvdmVyYWxsKQp0ZXN0X2RmIDwtIGRhdGEuZnJhbWUob3ZlcmFsbCA9IGFjb3VzdGljbmVzcy50ZXN0KQpiZXN0X2Fjb3VzdGljbmVzcy50ZXN0LnByZWRpY3QgPC0gcHJlZGljdChhY291c3RpY25lc3MubG0sIG5ld2RhdGE9dGVzdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQpiZXN0X2Fjb3VzdGljbmVzc190ZXN0LnByZWRpY3QubWFwZSA8LSBtYXBlKGJlc3RfYWNvdXN0aWNuZXNzLnRlc3RbMTpsZW5ndGgoYmVzdF9hY291c3RpY25lc3MudGVzdCldLCBiZXN0X2Fjb3VzdGljbmVzcy50ZXN0LnByZWRpY3RbLDFdKQpwbG90KGJlc3RfYWNvdXN0aWNuZXNzLHhsYWI9IlllYXIiLCB5bGFiPSJhY291c3RpY25lc3MiLCBtYWluPXBhc3RlKCJhY291c3RpY25lc3MgUG9wdWxhcml0eSBQcmVkaWN0aW9uIiksIHN1YiA9IHBhc3RlKCJNQVBFOiAiLCByb3VuZChiZXN0X2Fjb3VzdGljbmVzc190ZXN0LnByZWRpY3QubWFwZSoxMDAsIDMpLCAiJSIpKQpsaW5lcyh0cyhiZXN0X2Fjb3VzdGljbmVzcy50ZXN0LnByZWRpY3RbLDFdLCBzdGFydCA9IDE5MjErblRyYWluKzEsIGVuZCA9IDIwMjApLCBjb2wgPSAyKQoKI1ZhbGVuY2UKYmVzdF92YWxlbmNlID0gdHMoYXMubnVtZXJpYyhiZXN0X2ZlYXR1cmVfcXVhbnRpbGVzWywidmFsZW5jZSJdKSwgc3RhcnQ9MTkyMSwgZW5kPTIwMjApIApiZXN0X3ZhbGVuY2UudHJhaW4gPSBzdWJzZXQoYmVzdF92YWxlbmNlLCBzdGFydCA9IDEsIGVuZCA9IG5UcmFpbikKYmVzdF92YWxlbmNlLnRlc3QgPSBzdWJzZXQoYmVzdF92YWxlbmNlLCBzdGFydCA9IG5UcmFpbisxLCBlbmQgPSBuVHJhaW4rblRlc3QpCnRyYWluX2RmID0gZGF0YS5mcmFtZShiZXN0ID0gYmVzdF92YWxlbmNlLnRyYWluLCBvdmVyYWxsID0gdmFsZW5jZS50cmFpbikKCmJlc3QgPSBiZXN0X3ZhbGVuY2UudHJhaW4Kb3ZlcmFsbCA9IHZhbGVuY2UudHJhaW4KCnZhbGVuY2UubG0gPC0gbG0oYmVzdCB+IG92ZXJhbGwpCnRlc3RfZGYgPC0gZGF0YS5mcmFtZShvdmVyYWxsID0gdmFsZW5jZS50ZXN0KQpiZXN0X3ZhbGVuY2UudGVzdC5wcmVkaWN0IDwtIHByZWRpY3QodmFsZW5jZS5sbSwgbmV3ZGF0YT10ZXN0X2RmLCBpbnRlcnZhbD0icHJlZGljdGlvbiIpCmJlc3RfdmFsZW5jZV90ZXN0LnByZWRpY3QubWFwZSA8LSBtYXBlKGJlc3RfdmFsZW5jZS50ZXN0WzE6bGVuZ3RoKGJlc3RfdmFsZW5jZS50ZXN0KV0sIGJlc3RfdmFsZW5jZS50ZXN0LnByZWRpY3RbLDFdKQpwbG90KGJlc3RfdmFsZW5jZSx4bGFiPSJZZWFyIiwgeWxhYj0iVmFsZW5jZSIsIG1haW49cGFzdGUoIlZhbGVuY2UgUG9wdWxhcml0eSBQcmVkaWN0aW9uIiksIHN1YiA9IHBhc3RlKCJNQVBFOiAiLCByb3VuZChiZXN0X3ZhbGVuY2VfdGVzdC5wcmVkaWN0Lm1hcGUqMTAwLCAzKSwgIiUiKSkKbGluZXModHMoYmVzdF92YWxlbmNlLnRlc3QucHJlZGljdFssMV0sIHN0YXJ0ID0gMTkyMStuVHJhaW4rMSwgZW5kID0gMjAyMCksIGNvbCA9IDIpCgojVGVtcG8KYmVzdF90ZW1wbyA9IHRzKGFzLm51bWVyaWMoYmVzdF9mZWF0dXJlX3F1YW50aWxlc1ssInRlbXBvIl0pLCBzdGFydD0xOTIxLCBlbmQ9MjAyMCkgCmJlc3RfdGVtcG8udHJhaW4gPSBzdWJzZXQoYmVzdF90ZW1wbywgc3RhcnQgPSAxLCBlbmQgPSBuVHJhaW4pCmJlc3RfdGVtcG8udGVzdCA9IHN1YnNldChiZXN0X3RlbXBvLCBzdGFydCA9IG5UcmFpbisxLCBlbmQgPSBuVHJhaW4rblRlc3QpCnRyYWluX2RmID0gZGF0YS5mcmFtZShiZXN0ID0gYmVzdF90ZW1wby50cmFpbiwgb3ZlcmFsbCA9IHRlbXBvLnRyYWluKQoKYmVzdCA9IGJlc3RfdGVtcG8udHJhaW4Kb3ZlcmFsbCA9IHRlbXBvLnRyYWluCgp0ZW1wby5sbSA8LSBsbShiZXN0IH4gb3ZlcmFsbCkKdGVzdF9kZiA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSB0ZW1wby50ZXN0KQpiZXN0X3RlbXBvLnRlc3QucHJlZGljdCA8LSBwcmVkaWN0KHRlbXBvLmxtLCBuZXdkYXRhPXRlc3RfZGYsIGludGVydmFsPSJwcmVkaWN0aW9uIikKYmVzdF90ZW1wb190ZXN0LnByZWRpY3QubWFwZSA8LSBtYXBlKGJlc3RfdGVtcG8udGVzdFsxOmxlbmd0aChiZXN0X3RlbXBvLnRlc3QpXSwgYmVzdF90ZW1wby50ZXN0LnByZWRpY3RbLDFdKQpwbG90KGJlc3RfdGVtcG8seGxhYj0iWWVhciIsIHlsYWI9InRlbXBvIiwgbWFpbj1wYXN0ZSgidGVtcG8gUG9wdWxhcml0eSBQcmVkaWN0aW9uIiksIHN1YiA9IHBhc3RlKCJNQVBFOiAiLCByb3VuZChiZXN0X3RlbXBvX3Rlc3QucHJlZGljdC5tYXBlKjEwMCwgMyksICIlIikpCmxpbmVzKHRzKGJlc3RfdGVtcG8udGVzdC5wcmVkaWN0WywxXSwgc3RhcnQgPSAxOTIxK25UcmFpbisxLCBlbmQgPSAyMDIwKSwgY29sID0gMikKCiNMaXZlbmVzcwpiZXN0X2xpdmVuZXNzID0gdHMoYXMubnVtZXJpYyhiZXN0X2ZlYXR1cmVfcXVhbnRpbGVzWywibGl2ZW5lc3MiXSksIHN0YXJ0PTE5MjEsIGVuZD0yMDIwKSAKYmVzdF9saXZlbmVzcy50cmFpbiA9IHN1YnNldChiZXN0X2xpdmVuZXNzLCBzdGFydCA9IDEsIGVuZCA9IG5UcmFpbikKYmVzdF9saXZlbmVzcy50ZXN0ID0gc3Vic2V0KGJlc3RfbGl2ZW5lc3MsIHN0YXJ0ID0gblRyYWluKzEsIGVuZCA9IG5UcmFpbituVGVzdCkKdHJhaW5fZGYgPSBkYXRhLmZyYW1lKGJlc3QgPSBiZXN0X2xpdmVuZXNzLnRyYWluLCBvdmVyYWxsID0gbGl2ZW5lc3MudHJhaW4pCgpiZXN0ID0gYmVzdF9saXZlbmVzcy50cmFpbgpvdmVyYWxsID0gbGl2ZW5lc3MudHJhaW4KCmxpdmVuZXNzLmxtIDwtIGxtKGJlc3QgfiBvdmVyYWxsKQp0ZXN0X2RmIDwtIGRhdGEuZnJhbWUob3ZlcmFsbCA9IGxpdmVuZXNzLnRlc3QpCmJlc3RfbGl2ZW5lc3MudGVzdC5wcmVkaWN0IDwtIHByZWRpY3QobGl2ZW5lc3MubG0sIG5ld2RhdGE9dGVzdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQpiZXN0X2xpdmVuZXNzX3Rlc3QucHJlZGljdC5tYXBlIDwtIG1hcGUoYmVzdF9saXZlbmVzcy50ZXN0WzE6bGVuZ3RoKGJlc3RfbGl2ZW5lc3MudGVzdCldLCBiZXN0X2xpdmVuZXNzLnRlc3QucHJlZGljdFssMV0pCnBsb3QoYmVzdF9saXZlbmVzcyx4bGFiPSJZZWFyIiwgeWxhYj0ibGl2ZW5lc3MiLCBtYWluPXBhc3RlKCJsaXZlbmVzcyBQb3B1bGFyaXR5IFByZWRpY3Rpb24iKSwgc3ViID0gcGFzdGUoIk1BUEU6ICIsIHJvdW5kKGJlc3RfbGl2ZW5lc3NfdGVzdC5wcmVkaWN0Lm1hcGUqMTAwLCAzKSwgIiUiKSkKbGluZXModHMoYmVzdF9saXZlbmVzcy50ZXN0LnByZWRpY3RbLDFdLCBzdGFydCA9IDE5MjErblRyYWluKzEsIGVuZCA9IDIwMjApLCBjb2wgPSAyKQoKI0xvdWRuZXNzCmJlc3RfbG91ZG5lc3MgPSB0cyhhcy5udW1lcmljKGJlc3RfZmVhdHVyZV9xdWFudGlsZXNbLCJsb3VkbmVzcyJdKSwgc3RhcnQ9MTkyMSwgZW5kPTIwMjApIApiZXN0X2xvdWRuZXNzLnRyYWluID0gc3Vic2V0KGJlc3RfbG91ZG5lc3MsIHN0YXJ0ID0gMSwgZW5kID0gblRyYWluKQpiZXN0X2xvdWRuZXNzLnRlc3QgPSBzdWJzZXQoYmVzdF9sb3VkbmVzcywgc3RhcnQgPSBuVHJhaW4rMSwgZW5kID0gblRyYWluK25UZXN0KQp0cmFpbl9kZiA9IGRhdGEuZnJhbWUoYmVzdCA9IGJlc3RfbG91ZG5lc3MudHJhaW4sIG92ZXJhbGwgPSBsb3VkbmVzcy50cmFpbikKCmJlc3QgPSBiZXN0X2xvdWRuZXNzLnRyYWluCm92ZXJhbGwgPSBsb3VkbmVzcy50cmFpbgoKbG91ZG5lc3MubG0gPC0gbG0oYmVzdCB+IG92ZXJhbGwpCnRlc3RfZGYgPC0gZGF0YS5mcmFtZShvdmVyYWxsID0gbG91ZG5lc3MudGVzdCkKYmVzdF9sb3VkbmVzcy50ZXN0LnByZWRpY3QgPC0gcHJlZGljdChsb3VkbmVzcy5sbSwgbmV3ZGF0YT10ZXN0X2RmLCBpbnRlcnZhbD0icHJlZGljdGlvbiIpCmJlc3RfbG91ZG5lc3NfdGVzdC5wcmVkaWN0Lm1hcGUgPC0gbWFwZShiZXN0X2xvdWRuZXNzLnRlc3RbMTpsZW5ndGgoYmVzdF9sb3VkbmVzcy50ZXN0KV0sIGJlc3RfbG91ZG5lc3MudGVzdC5wcmVkaWN0WywxXSkKcGxvdChiZXN0X2xvdWRuZXNzLHhsYWI9IlllYXIiLCB5bGFiPSJsb3VkbmVzcyIsIG1haW49cGFzdGUoImxvdWRuZXNzIFBvcHVsYXJpdHkgUHJlZGljdGlvbiIpLCBzdWIgPSBwYXN0ZSgiTUFQRTogIiwgcm91bmQoYmVzdF9sb3VkbmVzc190ZXN0LnByZWRpY3QubWFwZSoxMDAsIDMpLCAiJSIpKQpsaW5lcyh0cyhiZXN0X2xvdWRuZXNzLnRlc3QucHJlZGljdFssMV0sIHN0YXJ0ID0gMTkyMStuVHJhaW4rMSwgZW5kID0gMjAyMCksIGNvbCA9IDIpCgojU3BlZWNoaW5lc3MKYmVzdF9zcGVlY2hpbmVzcyA9IHRzKGFzLm51bWVyaWMoYmVzdF9mZWF0dXJlX3F1YW50aWxlc1ssInNwZWVjaGluZXNzIl0pLCBzdGFydD0xOTIxLCBlbmQ9MjAyMCkgCmJlc3Rfc3BlZWNoaW5lc3MudHJhaW4gPSBzdWJzZXQoYmVzdF9zcGVlY2hpbmVzcywgc3RhcnQgPSAxLCBlbmQgPSBuVHJhaW4pCmJlc3Rfc3BlZWNoaW5lc3MudGVzdCA9IHN1YnNldChiZXN0X3NwZWVjaGluZXNzLCBzdGFydCA9IG5UcmFpbisxLCBlbmQgPSBuVHJhaW4rblRlc3QpCnRyYWluX2RmID0gZGF0YS5mcmFtZShiZXN0ID0gYmVzdF9zcGVlY2hpbmVzcy50cmFpbiwgb3ZlcmFsbCA9IHNwZWVjaGluZXNzLnRyYWluKQoKYmVzdCA9IGJlc3Rfc3BlZWNoaW5lc3MudHJhaW4Kb3ZlcmFsbCA9IHNwZWVjaGluZXNzLnRyYWluCgpzcGVlY2hpbmVzcy5sbSA8LSBsbShiZXN0IH4gb3ZlcmFsbCkKdGVzdF9kZiA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSBzcGVlY2hpbmVzcy50ZXN0KQpiZXN0X3NwZWVjaGluZXNzLnRlc3QucHJlZGljdCA8LSBwcmVkaWN0KHNwZWVjaGluZXNzLmxtLCBuZXdkYXRhPXRlc3RfZGYsIGludGVydmFsPSJwcmVkaWN0aW9uIikKYmVzdF9zcGVlY2hpbmVzc190ZXN0LnByZWRpY3QubWFwZSA8LSBtYXBlKGJlc3Rfc3BlZWNoaW5lc3MudGVzdFsxOmxlbmd0aChiZXN0X3NwZWVjaGluZXNzLnRlc3QpXSwgYmVzdF9zcGVlY2hpbmVzcy50ZXN0LnByZWRpY3RbLDFdKQpwbG90KGJlc3Rfc3BlZWNoaW5lc3MseGxhYj0iWWVhciIsIHlsYWI9InNwZWVjaGluZXNzIiwgbWFpbj1wYXN0ZSgic3BlZWNoaW5lc3MgUG9wdWxhcml0eSBQcmVkaWN0aW9uIiksIHN1YiA9IHBhc3RlKCJNQVBFOiAiLCByb3VuZChiZXN0X3NwZWVjaGluZXNzX3Rlc3QucHJlZGljdC5tYXBlKjEwMCwgMyksICIlIikpCmxpbmVzKHRzKGJlc3Rfc3BlZWNoaW5lc3MudGVzdC5wcmVkaWN0WywxXSwgc3RhcnQgPSAxOTIxK25UcmFpbisxLCBlbmQgPSAyMDIwKSwgY29sID0gMikKYGBgCgpQcmVkaWN0IGZlYXR1cmVzIGZvciB0aGUgb3ZlcmFsbCBtYXJrZXQgdHJlbmRzIGZvciB0aGUgbmV4dCBmaXZlIHllYXJzLgpXZSB3aWxsIG9ubHkgdXNlIG1vZGVscyB0aGF0IGhhZCB0aGUgbG93IGVub3VnaCBNQVBFcyAoMTAlIGNob3NlbiBhcyBhIHRocmVzaG9sZCkgYmFzZWQgb24gcG90ZW50aWFsIHRpbWVzZXJpZXMgbW9kZWxzLgoKCgpFbmVyZ3k6IE5OIE1vZGVsIApgYGB7cn0KZW5lcmd5Lm5uIDwtIGVsbShlbmVyZ3kpCmVuZXJneS5ubi5wcmVkaWN0IDwtIGZvcmVjYXN0KGVuZXJneS5ubiwgaD1uVGVzdCkKcGxvdChlbmVyZ3kubm4ucHJlZGljdCwgbWFpbj1wYXN0ZSgiRW5lcmd5IiwgIk5OIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyhlbmVyZ3kpCmBgYAoKRHVyYXRpb246IE5OIE1vZGVsCmBgYHtyfQpkdXJhdGlvbi5ubiA8LSBlbG0oZHVyYXRpb24pCmR1cmF0aW9uLm5uLnByZWRpY3QgPC0gZm9yZWNhc3QoZHVyYXRpb24ubm4sIGg9blRlc3QpCnBsb3QoZHVyYXRpb24ubm4ucHJlZGljdCwgbWFpbj1wYXN0ZSgiRHVyYXRpb24iLCAiTk4iKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmBgYAoKVGVtcG86IFNBUklNQSBNb2RlbCAKYGBge3J9CnRlbXBvLnNhcmltYSA8LSBhdXRvLmFyaW1hKHRlbXBvKQp0ZW1wby5zYXJpbWEucHJlZGljdCA8LSBmb3JlY2FzdCh0ZW1wby5zYXJpbWEsIGg9blRlc3QpCnBsb3QodGVtcG8uc2FyaW1hLnByZWRpY3QsIG1haW49cGFzdGUoIlRlbXBvIiwgIlNBUklNQSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXModGVtcG8pCmBgYAoKVmFsZW5jZTogU0FSSU1BIE1vZGVsIApgYGB7cn0KdmFsZW5jZS5zYXJpbWEgPC0gYXV0by5hcmltYSh2YWxlbmNlKQp2YWxlbmNlLnNhcmltYS5wcmVkaWN0IDwtIGZvcmVjYXN0KHZhbGVuY2Uuc2FyaW1hLCBoPW5UZXN0KQpwbG90KHZhbGVuY2Uuc2FyaW1hLnByZWRpY3QsIG1haW49cGFzdGUoIlZhbGVuY2UiLCAiU0FSSU1BIiksIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiKQpsaW5lcyh2YWxlbmNlKQpgYGAKCkxpdmVuZXNzOiBTQVJJTUEgTW9kZWwKYGBge3J9CmxpdmVuZXNzLnNhcmltYSA8LSBhdXRvLmFyaW1hKGxpdmVuZXNzKQpsaXZlbmVzcy5zYXJpbWEucHJlZGljdCA8LSBmb3JlY2FzdChsaXZlbmVzcy5zYXJpbWEsIGg9blRlc3QpCnBsb3QobGl2ZW5lc3Muc2FyaW1hLnByZWRpY3QsIG1haW49cGFzdGUoImxpdmVuZXNzIiwgIlNBUklNQSIpLCB4bGFiPSJZZWFyIiwgeWxhYj0iTWVkaWFuIFZhbHVlIikKbGluZXMobGl2ZW5lc3MpCmBgYAoKTG91ZG5lc3M6IFNFUyBNb2RlbCAKYGBge3J9CmxvdWRuZXNzLnNlcyA8LSBzZXMobG91ZG5lc3MsIGg9blRlc3QpCnBsb3QobG91ZG5lc3Muc2VzLCBtYWluPXBhc3RlKCJsb3VkbmVzcyIsICJTRVMiKSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIpCmxpbmVzKGxvdWRuZXNzKQpgYGAKCgoKCgpOb3cgcHJlZGljdGluZyB0aGUgbWVkaWFuIHZhbHVlcyAgZm9yIHRoZSAqbW9zdCBwb3B1bGFyIHNvbmdzKiBmb3IgdGhpcyBzZXQgb2YgZmVhdHVyZXMuCgpFbmVyZ3kKYGBge3J9CmJlc3QgPSBiZXN0X2VuZXJneQpvdmVyYWxsID0gZW5lcmd5CgoKZW5lcmd5LmxtIDwtIGxtKGJlc3QgfiBvdmVyYWxsKQpwcmVkaWN0X2RmIDwtIGRhdGEuZnJhbWUob3ZlcmFsbCA9IGVuZXJneS5ubi5wcmVkaWN0JG1lYW4pCmVuZXJneS5wcmVkaWN0IDwtIHByZWRpY3QoZW5lcmd5LmxtLCBuZXdkYXRhPXByZWRpY3RfZGYsIGludGVydmFsPSJwcmVkaWN0aW9uIikKCnNtYWxsZXN0ID0gbWluKG1pbihiZXN0KSwgbWluKG92ZXJhbGwpKQpiaWdnZXN0ID0gbWF4KG1heChiZXN0KSwgbWF4KG92ZXJhbGwpKQpwbG90KGVuZXJneSwgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIsIHlsaW09YyhzbWFsbGVzdCwgYmlnZ2VzdCksIG1haW49Ik1vc3QgUG9wdWxhciBTb25nJ3MgRW5lcmd5IEZlYXR1cmUgUHJlZGljdGlvbiIsIHN1Yj1wYXN0ZSgiUHJvamVjdGVkIG1lZGlhbiBmZWF0dXJlIHZhbHVlIGZvciBtb3N0IHBvcHVsYXIgc29uZ3M6IiwgcGFzdGUocm91bmQoZW5lcmd5Lm5uLnByZWRpY3QkbWVhbiwgMyksIGNvbGxhcHNlPSIsIikgKSkKbGluZXMoYmVzdF9lbmVyZ3ksIGNvbCA9IDIpCmxpbmVzKHRzKGVuZXJneS5wcmVkaWN0WywxXSwgc3RhcnQ9MjAyMCsxLCBlbmQ9MjAyMCtuVGVzdCksIGNvbCA9IDMpCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsdHk9MSwgY29sPWMoMSwyLDMpLCBsZWdlbmQ9YygiT3ZlcmFsbCIsICJNb3N0IFBvcHVsYXIiLCAiTW9zdCBQb3B1bGFyIFByb2plY3Rpb24iKSkKYGBgCgoKRHVyYXRpb24KYGBge3J9CmJlc3QgPSBiZXN0X2R1cmF0aW9uCm92ZXJhbGwgPSBkdXJhdGlvbgoKZHVyYXRpb24ubG0gPC0gbG0oYmVzdCB+IG92ZXJhbGwpCnByZWRpY3RfZGYgPC0gZGF0YS5mcmFtZShvdmVyYWxsID0gZHVyYXRpb24ubm4ucHJlZGljdCRtZWFuKQpkdXJhdGlvbi5wcmVkaWN0IDwtIHByZWRpY3QoZHVyYXRpb24ubG0sIG5ld2RhdGE9cHJlZGljdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQoKc21hbGxlc3QgPSBtaW4obWluKGJlc3QpLCBtaW4ob3ZlcmFsbCkpCmJpZ2dlc3QgPSBtYXgobWF4KGJlc3QpLCBtYXgob3ZlcmFsbCkpCnBsb3QoZHVyYXRpb24sIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiLCB5bGltPWMoc21hbGxlc3QsIGJpZ2dlc3QpLCBtYWluPSJNb3N0IFBvcHVsYXIgU29uZydzIER1cmF0aW9uIEZlYXR1cmUgUHJlZGljdGlvbiIsIHN1Yj1wYXN0ZSgiUHJvamVjdGVkIG1lZGlhbiBmZWF0dXJlIHZhbHVlIGZvciBtb3N0IHBvcHVsYXIgc29uZ3M6IiwgcGFzdGUocm91bmQoZHVyYXRpb24ubm4ucHJlZGljdCRtZWFuKSwgY29sbGFwc2U9IiwiKSApKQpsaW5lcyhiZXN0X2R1cmF0aW9uLCBjb2wgPSAyKQpsaW5lcyh0cyhkdXJhdGlvbi5wcmVkaWN0WywxXSwgc3RhcnQ9MjAyMCsxLCBlbmQ9MjAyMCtuVGVzdCksIGNvbCA9IDMpCmxlZ2VuZCgidG9wcmlnaHQiLCBsdHk9MSwgY29sPWMoMSwyLDMpLCBsZWdlbmQ9YygiT3ZlcmFsbCIsICJNb3N0IFBvcHVsYXIiLCAiTW9zdCBQb3B1bGFyIFByb2plY3Rpb24iKSkKYGBgCgoKVGVtcG8KYGBge3J9CmJlc3QgPSBiZXN0X3RlbXBvCm92ZXJhbGwgPSB0ZW1wbwoKdGVtcG8ubG0gPC0gbG0oYmVzdCB+IG92ZXJhbGwpCnByZWRpY3RfZGYgPC0gZGF0YS5mcmFtZShvdmVyYWxsID0gdGVtcG8uc2FyaW1hLnByZWRpY3QkbWVhbikKdGVtcG8ucHJlZGljdCA8LSBwcmVkaWN0KHRlbXBvLmxtLCBuZXdkYXRhPXByZWRpY3RfZGYsIGludGVydmFsPSJwcmVkaWN0aW9uIikKCgpzbWFsbGVzdCA9IG1pbihtaW4oYmVzdCksIG1pbihvdmVyYWxsKSkKYmlnZ2VzdCA9IG1heChtYXgoYmVzdCksIG1heChvdmVyYWxsKSkKcGxvdCh0ZW1wbywgeGxhYj0iWWVhciIsIHlsYWI9Ik1lZGlhbiBWYWx1ZSIsIHlsaW09YyhzbWFsbGVzdCwgYmlnZ2VzdCksIG1haW49Ik1vc3QgUG9wdWxhciBTb25nJ3MgVGVtcG8gRmVhdHVyZSBQcmVkaWN0aW9uIiwgc3ViPXBhc3RlKCJQcm9qZWN0ZWQgbWVkaWFuIGZlYXR1cmUgdmFsdWUgZm9yIG1vc3QgcG9wdWxhciBzb25nczoiLCBwYXN0ZShyb3VuZCh0ZW1wby5zYXJpbWEucHJlZGljdCRtZWFuKSwgY29sbGFwc2U9IiwiKSApKQpsaW5lcyhiZXN0X3RlbXBvLCBjb2wgPSAyKQpsaW5lcyh0cyh0ZW1wby5wcmVkaWN0WywxXSwgc3RhcnQ9MjAyMCsxLCBlbmQ9MjAyMCtuVGVzdCksIGNvbCA9IDMpCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsdHk9MSwgY29sPWMoMSwyLDMpLCBsZWdlbmQ9YygiT3ZlcmFsbCIsICJNb3N0IFBvcHVsYXIiLCAiTW9zdCBQb3B1bGFyIFByb2plY3Rpb24iKSkKYGBgCgoKVmFsZW5jZQpgYGB7cn0KYmVzdCA9IGJlc3RfdmFsZW5jZQpvdmVyYWxsID0gdmFsZW5jZQoKdmFsZW5jZS5sbSA8LSBsbShiZXN0IH4gb3ZlcmFsbCkKcHJlZGljdF9kZiA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSB2YWxlbmNlLnNhcmltYS5wcmVkaWN0JG1lYW4pCnZhbGVuY2UucHJlZGljdCA8LSBwcmVkaWN0KHZhbGVuY2UubG0sIG5ld2RhdGE9cHJlZGljdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQoKCnNtYWxsZXN0ID0gbWluKG1pbihiZXN0KSwgbWluKG92ZXJhbGwpKQpiaWdnZXN0ID0gbWF4KG1heChiZXN0KSwgbWF4KG92ZXJhbGwpKQpwbG90KHZhbGVuY2UsIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiLCB5bGltPWMoc21hbGxlc3QsIGJpZ2dlc3QpLCBtYWluPSJNb3N0IFBvcHVsYXIgU29uZydzIHZhbGVuY2UgRmVhdHVyZSBQcmVkaWN0aW9uIiwgc3ViPXBhc3RlKCJQcm9qZWN0ZWQgbWVkaWFuIGZlYXR1cmUgdmFsdWUgZm9yIG1vc3QgcG9wdWxhciBzb25nczoiLCBwYXN0ZShyb3VuZCh2YWxlbmNlLnNhcmltYS5wcmVkaWN0JG1lYW4sMyksIGNvbGxhcHNlPSIsIikgKSkKbGluZXMoYmVzdF92YWxlbmNlLCBjb2wgPSAyKQpsaW5lcyh0cyh2YWxlbmNlLnByZWRpY3RbLDFdLCBzdGFydD0yMDIwKzEsIGVuZD0yMDIwK25UZXN0KSwgY29sID0gMykKbGVnZW5kKCJib3R0b21yaWdodCIsIGx0eT0xLCBjb2w9YygxLDIsMyksIGxlZ2VuZD1jKCJPdmVyYWxsIiwgIk1vc3QgUG9wdWxhciIsICJNb3N0IFBvcHVsYXIgUHJvamVjdGlvbiIpKQpgYGAKCgpMaXZlbmVzcwpgYGB7cn0KYmVzdCA9IGJlc3RfbGl2ZW5lc3MKb3ZlcmFsbCA9IGxpdmVuZXNzCgpsaXZlbmVzcy5sbSA8LSBsbShiZXN0IH4gb3ZlcmFsbCkKcHJlZGljdF9kZiA8LSBkYXRhLmZyYW1lKG92ZXJhbGwgPSBsaXZlbmVzcy5zYXJpbWEucHJlZGljdCRtZWFuKQpsaXZlbmVzcy5wcmVkaWN0IDwtIHByZWRpY3QobGl2ZW5lc3MubG0sIG5ld2RhdGE9cHJlZGljdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQoKc21hbGxlc3QgPSBtaW4obWluKGJlc3QpLCBtaW4ob3ZlcmFsbCkpCmJpZ2dlc3QgPSBtYXgobWF4KGJlc3QpLCBtYXgob3ZlcmFsbCkpCnBsb3QobGl2ZW5lc3MsIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiLCB5bGltPWMoc21hbGxlc3QsIGJpZ2dlc3QpLCBtYWluPSJNb3N0IFBvcHVsYXIgU29uZydzIGxpdmVuZXNzIEZlYXR1cmUgUHJlZGljdGlvbiIsIHN1Yj1wYXN0ZSgiUHJvamVjdGVkIG1lZGlhbiBmZWF0dXJlIHZhbHVlIGZvciBtb3N0IHBvcHVsYXIgc29uZ3M6IiwgcGFzdGUocm91bmQobGl2ZW5lc3Muc2FyaW1hLnByZWRpY3QkbWVhbiwzKSwgY29sbGFwc2U9IiwiKSApKQpsaW5lcyhiZXN0X2xpdmVuZXNzLCBjb2wgPSAyKQpsaW5lcyh0cyhsaXZlbmVzcy5wcmVkaWN0WywxXSwgc3RhcnQ9MjAyMCsxLCBlbmQ9MjAyMCtuVGVzdCksIGNvbCA9IDMpCmxlZ2VuZCgidG9wcmlnaHQiLCBsdHk9MSwgY29sPWMoMSwyLDMpLCBsZWdlbmQ9YygiT3ZlcmFsbCIsICJNb3N0IFBvcHVsYXIiLCAiTW9zdCBQb3B1bGFyIFByb2plY3Rpb24iKSkKYGBgCgoKCkxvdWRuZXNzCmBgYHtyfQpiZXN0ID0gYmVzdF9sb3VkbmVzcwpvdmVyYWxsID0gbG91ZG5lc3MKCmxvdWRuZXNzLmxtIDwtIGxtKGJlc3QgfiBvdmVyYWxsKQpwcmVkaWN0X2RmIDwtIGRhdGEuZnJhbWUob3ZlcmFsbCA9IGxvdWRuZXNzLnNlcyRtZWFuKQpsb3VkbmVzcy5wcmVkaWN0IDwtIHByZWRpY3QobG91ZG5lc3MubG0sIG5ld2RhdGE9cHJlZGljdF9kZiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQoKc21hbGxlc3QgPSBtaW4obWluKGJlc3QpLCBtaW4ob3ZlcmFsbCkpCmJpZ2dlc3QgPSBtYXgobWF4KGJlc3QpLCBtYXgob3ZlcmFsbCkpCnBsb3QobG91ZG5lc3MsIHhsYWI9IlllYXIiLCB5bGFiPSJNZWRpYW4gVmFsdWUiLCB5bGltPWMoc21hbGxlc3QsIGJpZ2dlc3QpLCBtYWluPSJNb3N0IFBvcHVsYXIgU29uZydzIGxvdWRuZXNzIEZlYXR1cmUgUHJlZGljdGlvbiIsIHN1Yj1wYXN0ZSgiUHJvamVjdGVkIG1lZGlhbiBmZWF0dXJlIHZhbHVlIGZvciBtb3N0IHBvcHVsYXIgc29uZ3M6IiwgcGFzdGUocm91bmQobG91ZG5lc3Muc2VzJG1lYW4sMSksIGNvbGxhcHNlPSIsIikgKSkKbGluZXMoYmVzdF9sb3VkbmVzcywgY29sID0gMikKbGluZXModHMobG91ZG5lc3MucHJlZGljdFssMV0sIHN0YXJ0PTIwMjArMSwgZW5kPTIwMjArblRlc3QpLCBjb2wgPSAzKQpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgbHR5PTEsIGNvbD1jKDEsMiwzKSwgbGVnZW5kPWMoIk92ZXJhbGwiLCAiTW9zdCBQb3B1bGFyIiwgIk1vc3QgUG9wdWxhciBQcm9qZWN0aW9uIikpCmBgYA==